def calculate_metrics(in_file: Path, out_files: dict): """ Calculate various DWI metrics based on Diffusion's kurtosis tensor estimation. Parameters ---------- in_file : Path Path to preprocessed DWI series out_files : dict A dictionary containing paths to various metrics to be calculated """ tensor = out_files.pop("tensor") tsr = mrt.FitTensor() tsr.inputs.in_file = in_file tsr.inputs.out_file = tensor tsr.inputs.args = "-quiet" if not tensor.exists(): tsr.run() comp = mrt.TensorMetrics() comp.inputs.in_file = tensor comp_args = "" for key, val in out_files.items(): comp_args += f"-{key} {val} " comp.inputs.args = comp_args return tsr, comp
def fit_tensors(dwi_file: str, mask_file: str, dti_file: str): dilated_mask = mask_file.replace(".mif", "_dilated.mif") cmd = f"maskfilter {mask_file} dilate {dilated_mask} -npass 3" os.system(cmd) tsr = mrt.FitTensor() tsr.inputs.in_file = dwi_file tsr.inputs.in_mask = dilated_mask tsr.inputs.out_file = dti_file print(tsr.cmdline) tsr.run() comp = mrt.TensorMetrics() comp.inputs.in_file = dti_file comp.inputs.out_fa = dti_file.replace("dti", "fa") comp.inputs.args = "-force" print(comp.cmdline) comp.run() return comp.inputs.out_fa
def fit_tensors(dwi_file: Path, mask_file: Path, dti_file: Path, fa_file: Path): dilated_mask = mask_file.parent / f"{mask_file.stem}_dilated.mif" cmd = f"maskfilter {mask_file} dilate {dilated_mask} -npass 3" os.system(cmd) tsr = mrt.FitTensor() tsr.inputs.in_file = dwi_file tsr.inputs.in_mask = dilated_mask tsr.inputs.out_file = dti_file print(tsr.cmdline) tsr.run() comp = mrt.TensorMetrics() comp.inputs.in_file = dti_file comp.inputs.out_fa = fa_file print(comp.cmdline) comp.run() dti_file.unlink() return comp.inputs.out_fa
def create_DWI_workflow( subject_list, bids_dir, work_dir, out_dir, bids_templates, ): # create initial workflow wf = Workflow(name='DWI', base_dir=work_dir) # use infosource to iterate workflow across subject list n_infosource = Node(interface=IdentityInterface(fields=['subject_id']), name="subject_source" # input: 'subject_id' # output: 'subject_id' ) # runs the node with subject_id = each element in subject_list n_infosource.iterables = ('subject_id', subject_list) # select matching files from bids_dir n_selectfiles = Node(interface=SelectFiles(templates=bids_templates, base_directory=bids_dir), name='get_subject_data') wf.connect([(n_infosource, n_selectfiles, [('subject_id', 'subject_id_p')]) ]) # DWIDenoise # https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.mrtrix3.preprocess.html n_denoise = Node(interface=mrt.DWIDenoise(), name='n_denoise') wf.connect([(n_selectfiles, n_denoise, [('DWI_all', 'in_file')])]) # datasink n_datasink = Node(interface=DataSink(base_directory=out_dir), name='datasink') wf.connect([(n_selectfiles, n_datasink, [('all_b0_PA', 'all_b0_PA_unchanged')]), (n_denoise, n_datasink, [('out_file', 'DWI_all_denoised')])]) ########## I'VE ADDED IN ########################################################################## # MRDeGibbs # https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.mrtrix3.preprocess.html n_degibbs = Node( interface=mrt.MRDeGibbs(out_file='DWI_all_denoised_degibbs.mif'), name='n_degibbs') wf.connect([(n_denoise, n_degibbs, [('out_file', 'in_file')])]) wf.connect([(n_degibbs, n_datasink, [('out_file', 'DWI_all_denoised_degibbs.mif')])]) # DWI Extract n_dwiextract = Node(interface=mrt.DWIExtract(bzero=True, out_file='b0vols.mif'), name='n_dwiextract') wf.connect([(n_degibbs, n_dwiextract, [('out_file', 'in_file')])]) wf.connect([(n_dwiextract, n_datasink, [('out_file', 'noddi_b0_degibbs')]) ]) # MRcat n_mrcat = Node( interface=mrcatfunc.MRCat( #axis=3, out_file='b0s.mif'), name='n_mrcat') # Connect DTI_B0_PA to mrcat node wf.connect([(n_selectfiles, n_mrcat, [('DTI_B0_PA', 'in_file1')])]) wf.connect([(n_dwiextract, n_mrcat, [('out_file', 'in_file2')])]) # Output the mrcat file into file 'noddi_and_PA_b0s.mif' wf.connect([(n_mrcat, n_datasink, [('out_file', 'noddi_and_PA_b0s.mif')])]) # DWIfslpreproc n_dwifslpreproc = Node(interface=preprocfunc.DWIFslPreProc( out_file='preprocessedDWIs.mif', use_header=True), name='n_dwifslpreproc') # Connect output of degibbs to dwifslpreproc node wf.connect([(n_degibbs, n_dwifslpreproc, [('out_file', 'in_file')])]) # Connect output of mrcat to se_epi input wf.connect([(n_mrcat, n_dwifslpreproc, [('out_file', 'se_epi_file')])]) # Put output of dwifslpreproc into 'preprocessedDWIs.mif' wf.connect([(n_dwifslpreproc, n_datasink, [('out_file', 'preprocessedDWIs.mif')])]) # DWI bias correct n_dwibiascorrect = Node( interface=preprocess.DWIBiasCorrect(use_ants=True), name='n_dwibiascorrect', ) wf.connect([(n_dwifslpreproc, n_dwibiascorrect, [('out_file', 'in_file')]) ]) wf.connect([(n_dwibiascorrect, n_datasink, [('out_file', 'ANTSpreprocessedDWIs.mif')])]) #DWI2mask n_dwi2mask = Node(interface=mrt.BrainMask(out_file='mask.mif'), name='n_dwi2mask') wf.connect([(n_dwibiascorrect, n_dwi2mask, [('out_file', 'in_file')])]) wf.connect([(n_dwi2mask, n_datasink, [('out_file', 'mask.mif')])]) ## A) Fixel-based analysis #DWI2response n_dwi2response = Node(interface=mrt.ResponseSD(algorithm='dhollander', wm_file='wm_res.txt', gm_file='gm_res.txt', csf_file='csf_res.txt'), name='n_dwi2response') wf.connect([(n_dwibiascorrect, n_dwi2response, [('out_file', 'in_file')])]) wf.connect([(n_dwi2response, n_datasink, [('wm_file', 'wm_res.txt')])]) wf.connect([(n_dwi2response, n_datasink, [('gm_file', 'gm_res.txt')])]) wf.connect([(n_dwi2response, n_datasink, [('csf_file', 'csf_res.txt')])]) #DWI2fod n_dwi2fod = Node(interface=mrt.ConstrainedSphericalDeconvolution( algorithm='msmt_csd', wm_odf='wmfod.mif', gm_odf='gmfod.mif', csf_odf='csffod.mif'), name='n_dwi2fod') # connect outputs of dwi2fod into dwi2response wf.connect([(n_dwibiascorrect, n_dwi2fod, [('out_file', 'in_file')])]) wf.connect([(n_dwi2response, n_dwi2fod, [('wm_file', 'wm_txt')])]) wf.connect([(n_dwi2response, n_dwi2fod, [('gm_file', 'gm_txt')])]) wf.connect([(n_dwi2response, n_dwi2fod, [('csf_file', 'csf_txt')])]) # output wmfod file from dwi2fod wf.connect([(n_dwi2fod, n_datasink, [('wm_odf', 'wmfod.mif')])]) wf.connect([(n_dwi2fod, n_datasink, [('gm_odf', 'gmfod.mif')])]) wf.connect([(n_dwi2fod, n_datasink, [('csf_odf', 'csffod.mif')])]) #mrconvert to extract Z component of wmfod n_mrconvert_fod = Node(interface=utils.MRConvert(out_file='Zwmfod.mif', coord=[3, 0]), name='n_mrconvert_fod') wf.connect([(n_dwi2fod, n_mrconvert_fod, [('wm_odf', 'in_file')])]) wf.connect([(n_mrconvert_fod, n_datasink, [('out_file', 'Zwmfod.mif')])]) # Concatenate all wm, gm, csf fod files to see their distribution throughout Brain n_mrcat_fod = Node(interface=mrcatfunc.MRCat(out_file='vf.mif'), name='n_mrcat_fod') # Connect Zwmfod, gmfod and csffod as inputs wf.connect([(n_mrconvert_fod, n_mrcat_fod, [('out_file', 'in_file1')])]) wf.connect([(n_dwi2fod, n_mrcat_fod, [('gm_odf', 'in_file2')])]) wf.connect([(n_dwi2fod, n_mrcat_fod, [('csf_odf', 'in_file3')])]) # Output the mrcat file into file into 'vf.mif' wf.connect([(n_mrcat_fod, n_datasink, [('out_file', 'vf.mif')])]) #fod2fixel wmfod.mif wmfixels -fmls_peak_value 0 -fmls_integral 0.10 -afd afd.mif -peak peak.mif -disp disp.mif # OUTPUTS: -afd afd.mif -peak peak.mif -disp disp.mif n_fod2fixel = Node( interface=fod2fixelfunc.fod2fixel( out_file='wmfixels', #afd_file = 'afd.mif', peak_file='peak.mif', disp_file='disp.mif'), name='n_fod2fixel') # let the peak value parameter be trialed as multiple values n_fod2fixel.iterables = ('fmls_peak_value', [0, 0.10, 0.50]) n_fod2fixel.iterables = ('fmls_integral', [0, 0.10, 0.50]) # obtain wm fibre image as input wf.connect([(n_dwi2fod, n_fod2fixel, [('wm_odf', 'in_file')])]) # ouputs of fod2fixel wf.connect([(n_fod2fixel, n_datasink, [('out_file', 'wmfixels')])]) wf.connect([(n_fod2fixel, n_datasink, [('afd_file', 'afd.mif')])]) wf.connect([(n_fod2fixel, n_datasink, [('peak_file', 'peak.mif')])]) wf.connect([(n_fod2fixel, n_datasink, [('disp_file', 'disp.mif')])]) ## Fixel2peaks n_fixel2peaks = Node(interface=fixel2peaksfunc.fixel2peaks( out_file='peaks_wmdirections.mif'), name='n_fixel2peaks') n_fixel2peaks.iterables = ('number', [1, 2, 3]) # obtain directions file in output folder of fod2fixel, as input wf.connect([(n_fod2fixel, n_fixel2peaks, [('out_file', 'in_file')])]) # ouputs of fixel2peaks wf.connect([(n_fixel2peaks, n_datasink, [('out_file', 'peaks_wmdirections.mif')])]) #mrmath to find normalised value of peak WM directions n_mrmath = Node(interface=mrt.MRMath( axis=3, operation='norm', out_file='norm_peaks_wmdirections.mif'), name='n_mrmath') wf.connect([(n_fixel2peaks, n_mrmath, [('out_file', 'in_file')])]) wf.connect([(n_mrmath, n_datasink, [('out_file', 'norm_peaks_wmdirections.mif')])]) # mrcalc to divide peak WM direction by normalised value n_mrcalc = Node(interface=mrcalcfunc.MRCalc(operation='divide', out_file='wm_peak_dir.mif'), name='n_mrcalc') wf.connect([(n_fixel2peaks, n_mrcalc, [('out_file', 'in_file1')])]) wf.connect([(n_mrmath, n_mrcalc, [('out_file', 'in_file2')])]) wf.connect([(n_mrcalc, n_datasink, [('out_file', 'WM_peak_dir.mif')])]) #mrconvert to extract Z component of peak directions n_mrconvert2 = Node(interface=utils.MRConvert( out_file='Zpeak_WM_Directions.mif', coord=[3, 2]), name='n_mrconvert2') wf.connect([(n_mrcalc, n_mrconvert2, [('out_file', 'in_file')])]) wf.connect([(n_mrconvert2, n_datasink, [('out_file', 'Zpeak_WM_Directions.mif')])]) # mrcalc to find absolute value n_mrcalc2 = Node(interface=mrcalcfunc.MRCalc( operation='abs', out_file='absZpeak_WM_Directions.mif'), name='n_mrcalc2') wf.connect([(n_mrconvert2, n_mrcalc2, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc2, n_datasink, [('out_file', 'absZpeak_WM_Directions.mif')])]) # mrcalc to get angle by doing inverse cosine n_mrcalc3 = Node(interface=mrcalcfunc.MRCalc( operation='acos', out_file='acosZpeak_WM_Directions.mif'), name='n_mrcalc3') wf.connect([(n_mrcalc2, n_mrcalc3, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc3, n_datasink, [('out_file', 'acosZpeak_WM_Directions.mif')])]) # mrcalc to convert angle to degrees n_mrcalc4 = Node(interface=mrcalcfunc.MRCalc( operation='multiply', operand=180, out_file='Fixel1_Z_angle.mif'), name='n_mrcalc4') wf.connect([(n_mrcalc3, n_mrcalc4, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc4, n_datasink, [('out_file', 'Fixel1_Z_angle.mif')])]) n_mrcalc5 = Node(interface=mrcalcfunc.MRCalc( operation='divide', operand=3.14159265, out_file='Fixel1_Z_cos_deg.mif'), name='n_mrcalc5') wf.connect([(n_mrcalc4, n_mrcalc5, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc5, n_datasink, [('out_file', 'Fixel1_Z_cos_deg.mif')]) ]) ## B) Tensor-based analysis #dwi2tensor n_dwi2tensor = Node(interface=mrt.FitTensor(out_file='dti.mif'), name='n_dwi2tensor') wf.connect([(n_dwibiascorrect, n_dwi2tensor, [('out_file', 'in_file')])]) wf.connect([(n_dwi2mask, n_dwi2tensor, [('out_file', 'in_mask')])]) wf.connect([(n_dwi2tensor, n_datasink, [('out_file', 'dt.mif')])]) #tensor2metric n_tensor2metric = Node(interface=tensor2metricfunc.tensor2metric( modulate='none', num=1, vector_file='eigenvector.mif'), name='n_tensor2metric') wf.connect([(n_dwi2tensor, n_tensor2metric, [('out_file', 'input_file')])]) wf.connect([(n_tensor2metric, n_datasink, [('vector_file', 'eigenvector.mif')])]) #mrconvert to get Z eigenvector n_mrconvert3 = Node(interface=utils.MRConvert(coord=[3, 2], out_file='eigenvectorZ.mif'), name='n_mrconvert3') wf.connect([(n_tensor2metric, n_mrconvert3, [('vector_file', 'in_file')])]) wf.connect([(n_mrconvert3, n_datasink, [('out_file', 'eigenvectorZ.mif')]) ]) #ALL SUBSEQUENT STEPS GET ANGLE IN DEGREES # mrcalc to find absolute value n_mrcalc6 = Node(interface=mrcalcfunc.MRCalc( operation='abs', out_file='abs_eigenvectorZ.mif'), name='n_mrcalc6') wf.connect([(n_mrconvert3, n_mrcalc6, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc6, n_datasink, [('out_file', 'abs_eigenvectorZ.mif')]) ]) # mrcalc to get angle by doing inverse cosine n_mrcalc7 = Node(interface=mrcalcfunc.MRCalc( operation='acos', out_file='acos_eigenvectorZ.mif'), name='n_mrcalc7') wf.connect([(n_mrcalc6, n_mrcalc7, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc7, n_datasink, [('out_file', 'acos_eigenvectorZ.mif') ])]) # mrcalc to convert angle to degrees n_mrcalc8 = Node( interface=mrcalcfunc.MRCalc(operation='multiply', operand=180, out_file='degrees_eigenvectorZ.mif'), name='n_mrcalc8') wf.connect([(n_mrcalc7, n_mrcalc8, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc8, n_datasink, [('out_file', 'degrees_eigenvectorZ.mif')])]) n_mrcalc9 = Node(interface=mrcalcfunc.MRCalc(operation='divide', operand=3.14159265, out_file='dti_z_cos_deg.mif'), name='n_mrcalc9') wf.connect([(n_mrcalc8, n_mrcalc9, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc9, n_datasink, [('out_file', 'dti_z_cos_deg.mif')])]) # Difference image between fixel based and tensor based outputs n_mrcalc10 = Node(interface=mrcalcfunc.MRCalc( operation='subtract', out_file='diff_imag_tensor_minus_fixel.mif'), name='n_mrcalc10') wf.connect([(n_mrcalc9, n_mrcalc10, [('out_file', 'in_file1')])]) wf.connect([(n_mrcalc5, n_mrcalc10, [('out_file', 'in_file2')])]) wf.connect([(n_mrcalc10, n_datasink, [('out_file', 'diff_imag_tensor_minus_fixel.mif')])]) #################################################################################3 return wf
def dholl_preproc_wf(shells=[0, 1000, 2000], lmax=[0, 8, 8], sshell=False, noreorient=False, template_dir=None, template_label=None, wdir=None, nthreads=1, name='dholl_preproc_wf'): """ Set up Dhollander response preproc workflow No assumption of registration to T1w space is made """ if template_dir is None or template_label is None: print("Missing template info") raise IOError # Grab template data templateGrabber = io.getTemplate(template_dir=template_dir, template_label=template_label, wdir=wdir) # Convert nii to mif dwiConvert = pe.Node(mrt.MRConvert(), name='dwiConvert') dwiConvert.base_dir = wdir dwiConvert.inputs.nthreads = nthreads dwiConvert.interface.num_threads = nthreads # dwi2response - included but not used dwi2response = pe.Node(mrt.ResponseSD(), name='dwi2response') dwi2response.base_dir = wdir dwi2response.inputs.algorithm = 'dhollander' dwi2response.inputs.wm_file = 'space-dwi_model-CSD_WMResp.txt' dwi2response.inputs.gm_file = 'space-dwi_model-CSD_GMResp.txt' dwi2response.inputs.csf_file = 'space-dwi_model-CSD_CSFResp.txt' dwi2response.inputs.max_sh = lmax dwi2response.inputs.shell = shells dwi2response.inputs.nthreads = nthreads dwi2response.interface.num_threads = nthreads # Convert mask (nii) to mif maskConvert = pe.Node(mrt.MRConvert(), name='maskConvert') maskConvert.base_dir = wdir maskConvert.inputs.nthreads = nthreads maskConvert.interface.num_threads = nthreads # dwi2fod dwi2fod = pe.Node(mrt.EstimateFOD(), name='dwi2fod') dwi2fod.base_dir = wdir dwi2fod.inputs.algorithm = 'msmt_csd' dwi2fod.inputs.shell = shells dwi2fod.inputs.wm_odf = 'space-dwi_model-CSD_WMFOD.mif' if sshell is False: dwi2fod.inputs.gm_odf = 'space-dwi_model-CSD_GMFOD.mif' dwi2fod.inputs.csf_odf = 'space-dwi_model-CSD_CSFFOD.mif' dwi2fod.inputs.nthreads = nthreads dwi2fod.interface.num_threads = nthreads # mtnormalise mtnormalise = pe.Node(mrt.MTNormalise(), name='mtnormalise') mtnormalise.base_dir = wdir mtnormalise.inputs.out_wm = 'space-dwi_model-CSD_WMFODNorm.mif' if sshell is False: mtnormalise.inputs.out_gm = 'space-dwi_model-CSD_GMFODNorm.mif' mtnormalise.inputs.out_csf = 'space-dwi_model-CSD_CSFFODNorm.mif' mtnormalise.inputs.nthreads = nthreads mtnormalise.interface.num_threads = nthreads # Registration MRRegister = pe.Node(mrt.MRRegister(), name='MRRegister') MRRegister.base_dir = wdir # MRRegister.inputs.ref_file = template MRRegister.inputs.nl_warp = [ 'from-dwi_to-Template_xfm.mif', 'from-Template_to-dwi_xfm.mif' ] if noreorient is not False: MRRegister.inputs.noreorientation = noreorient MRRegister.inputs.nthreads = nthreads MRRegister.interface.num_threads = nthreads # Transforms WarpSelect1 = pe.Node(niu.Select(), name='WarpSelect1') WarpSelect1.base_dir = wdir WarpSelect1.inputs.index = [0] WarpSelect1.interface.num_threads = nthreads WarpSelect2 = pe.Node(niu.Select(), name='WarpSelect2') WarpSelect2.base_dir = wdir WarpSelect2.inputs.index = [1] WarpSelect2.interface.num_threads = nthreads # Warp data MaskTransform = pe.Node(mrt.MRTransform(), name='MaskTransform') MaskTransform.base_dir = wdir MaskTransform.inputs.out_file = 'space-Template_brainmask.mif' MaskTransform.inputs.nthreads = nthreads MaskTransform.interface.num_threads = nthreads FODTransform = pe.Node(mrt.MRTransform(), name='FODTransform') FODTransform.base_dir = wdir FODTransform.inputs.out_file = 'space-Template_model-CSD_WMFODNorm.mif' FODTransform.inputs.nthreads = nthreads FODTransform.interface.num_threads = nthreads # Tensor processing DWINormalise = pe.Node(mrt.DWINormalise(), name='DWINormalise') DWINormalise.base_dir = wdir DWINormalise.inputs.out_file = 'space-dwi_dwiNorm.mif' DWINormalise.inputs.nthreads = nthreads DWINormalise.interface.num_threads = nthreads DWITransform = pe.Node(mrt.MRTransform(), name='DWITransform') DWITransform.base_dir = wdir DWITransform.inputs.out_file = 'space-Template_dwiNorm.mif' DWITransform.inputs.nthreads = nthreads DWITransform.interface.num_threads = nthreads FitTensor = pe.Node(mrt.FitTensor(), name='FitTensor') FitTensor.base_dir = wdir FitTensor.inputs.out_file = 'space-Template_desc-WLS_model-DTI_tensor.mif' FitTensor.inputs.nthreads = nthreads FitTensor.interface.num_threads = nthreads TensorMetrics = pe.Node(mrt.TensorMetrics(), name='TensorMetrics') TensorMetrics.base_dir = wdir TensorMetrics.inputs.out_fa = 'space-Template_model-DTI_FA.mif' TensorMetrics.inputs.out_adc = 'space-Template_model-DTI_MD.mif' TensorMetrics.inputs.out_ad = 'space-Template_model-DTI_AD.mif' TensorMetrics.inputs.out_rd = 'space-Template_model-DTI_RD.mif' TensorMetrics.inputs.nthreads = nthreads TensorMetrics.interface.num_threads = nthreads # Build workflow workflow = pe.Workflow(name=name) # Single shell if sshell is True: workflow.connect([ # Compute FOD (dwiConvert, dwi2response, [('out_file', 'in_file')]), (dwiConvert, dwi2fod, [('out_file', 'in_file')]), (dwi2response, dwi2fod, [('wm_file', 'wm_txt'), ('csf_file', 'csf_txt')]), (dwi2fod, mtnormalise, [('wm_odf', 'in_wm'), ('csf_odf', 'in_csf')]), (maskConvert, dwi2response, [('out_file', 'in_mask')]), (maskConvert, dwi2fod, [('out_file', 'mask_file')]), (maskConvert, mtnormalise, [('out_file', 'mask')]), (maskConvert, MRRegister, [('out_file', 'mask1')]), (templateGrabber, MRRegister, [('wm_fod', 'ref_file'), ('mask', 'mask2')]), (mtnormalise, MRRegister, [('out_wm', 'in_file')]), (MRRegister, WarpSelect1, [('nl_warp', 'inlist')]), (MRRegister, WarpSelect2, [('nl_warp', 'inlist')]), (maskConvert, MaskTransform, [('out_file', 'in_file')]), (WarpSelect1, MaskTransform, [('out', 'warp')]), (mtnormalise, FODTransform, [('out_wm', 'in_file')]), (WarpSelect1, FODTransform, [('out', 'warp')]), # Compute tensors (dwiConvert, DWINormalise, [('out_file', 'in_file')]), (maskConvert, DWINormalise, [('out_file', 'in_mask')]), (DWINormalise, DWITransform, [('out_file', 'in_file')]), (WarpSelect1, DWITransform, [('out', 'warp')]), (DWITransform, FitTensor, [('out_file', 'in_file')]), (MaskTransform, FitTensor, [('out_file', 'in_mask')]), (FitTensor, TensorMetrics, [('out_file', 'in_file')]), (MaskTransform, TensorMetrics, [('out_file', 'in_mask')]) ]) # For multi-shell else: workflow.connect([ # Compute FOD (dwiConvert, dwi2response, [('out_file', 'in_file')]), (dwiConvert, dwi2fod, [('out_file', 'in_file')]), (dwi2response, dwi2fod, [('wm_file', 'wm_txt'), ('gm_file', 'gm_txt'), ('csf_file', 'csf_txt')]), (dwi2fod, mtnormalise, [('wm_odf', 'in_wm'), ('gm_odf', 'in_gm'), ('csf_odf', 'in_csf')]), (maskConvert, dwi2response, [('out_file', 'in_mask')]), (maskConvert, dwi2fod, [('out_file', 'mask_file')]), (maskConvert, mtnormalise, [('out_file', 'mask')]), (maskConvert, MRRegister, [('out_file', 'mask1')]), (templateGrabber, MRRegister, [('wm_fod', 'ref_file'), ('mask', 'mask2')]), (mtnormalise, MRRegister, [('out_wm', 'in_file')]), (MRRegister, WarpSelect1, [('nl_warp', 'inlist')]), (MRRegister, WarpSelect2, [('nl_warp', 'inlist')]), (maskConvert, MaskTransform, [('out_file', 'in_file')]), (WarpSelect1, MaskTransform, [('out', 'warp')]), (mtnormalise, FODTransform, [('out_wm', 'in_file')]), (WarpSelect1, FODTransform, [('out', 'warp')]), # Compute tensors (dwiConvert, DWINormalise, [('out_file', 'in_file')]), (maskConvert, DWINormalise, [('out_file', 'in_mask')]), (DWINormalise, DWITransform, [('out_file', 'in_file')]), (WarpSelect1, DWITransform, [('out', 'warp')]), (DWITransform, FitTensor, [('out_file', 'in_file')]), (MaskTransform, FitTensor, [('out_file', 'in_mask')]), (FitTensor, TensorMetrics, [('out_file', 'in_file')]), (MaskTransform, TensorMetrics, [('out_file', 'in_mask')]) ]) return workflow
def create_DWI_workflow( subject_list, bids_dir, work_dir, out_dir, bids_templates, ): # create initial workflow wf = Workflow(name='DWI', base_dir=work_dir) # use infosource to iterate workflow across subject list n_infosource = Node(interface=IdentityInterface(fields=['subject_id']), name="subject_source" # input: 'subject_id' # output: 'subject_id' ) # runs the node with subject_id = each element in subject_list n_infosource.iterables = ('subject_id', subject_list) # select matching files from bids_dir n_selectfiles = Node(interface=SelectFiles(templates=bids_templates, base_directory=bids_dir), name='get_subject_data') wf.connect([(n_infosource, n_selectfiles, [('subject_id', 'subject_id_p')]) ]) ########## IMPLEMENTING MRTRIX COMMANDS FOR IMAGE ANALYSIS ####################################### ## 1) Preprocessing of Data # https://nipype.readthedocs.io/en/latest/api/generated/nipype.interfaces.mrtrix3.preprocess.html # DWIDenoise to remove Gaussian noise n_denoise = Node(interface=mrt.DWIDenoise(), name='n_denoise') wf.connect([(n_selectfiles, n_denoise, [('DWI_all', 'in_file')])]) # datasink n_datasink = Node(interface=DataSink(base_directory=out_dir), name='datasink') # output denoised data into 'DWI_all_denoised' wf.connect([(n_selectfiles, n_datasink, [('all_b0_PA', 'all_b0_PA_unchanged')]), (n_denoise, n_datasink, [('out_file', 'DWI_all_denoised')])]) # MRDeGibbs to remove Gibbs ringing artifact n_degibbs = Node( interface=mrt.MRDeGibbs(out_file='DWI_all_denoised_degibbs.mif'), name='n_degibbs') # input denoised data into degibbs function wf.connect([(n_denoise, n_degibbs, [('out_file', 'in_file')])]) # output degibbs data into 'DWI_all_denoised_degibbs.mif' wf.connect([(n_degibbs, n_datasink, [('out_file', 'DWI_all_denoised_degibbs.mif')])]) # DWI Extract to extract b0 volumes from multi-b image data n_dwiextract = Node(interface=mrt.DWIExtract(bzero=True, out_file='b0vols.mif'), name='n_dwiextract') # input degibbs data into dwiextract function wf.connect([(n_degibbs, n_dwiextract, [('out_file', 'in_file')])]) # output extracted b0 volume from degibbs data (contains multiple b values) wf.connect([(n_dwiextract, n_datasink, [('out_file', 'noddi_b0_degibbs')]) ]) # MRcat to combine b0 volumes from input image and reverse phase encoded data n_mrcat = Node( interface=mrcatfunc.MRCat( #axis=3, out_file='b0s.mif'), name='n_mrcat') # input DTI images (all b0 volumes; reverse phase encoded) for concatenating wf.connect([(n_selectfiles, n_mrcat, [('DTI_B0_PA', 'in_file1')])]) # input b0 volumes from NODDI data for concatenating wf.connect([(n_dwiextract, n_mrcat, [('out_file', 'in_file2')])]) # output the mrcat file into 'noddi_and_PA_b0s.mif' wf.connect([(n_mrcat, n_datasink, [('out_file', 'noddi_and_PA_b0s.mif')])]) # DWIfslpreproc for image pre-processing using FSL's eddy tool n_dwifslpreproc = Node(interface=preprocfunc.DWIFslPreProc( out_file='preprocessedDWIs.mif', use_header=True), name='n_dwifslpreproc') # output of degibbs as input for preprocessing wf.connect([(n_degibbs, n_dwifslpreproc, [('out_file', 'in_file')])]) # output of mrcat (extracted b0 volumes) as se_epi input wf.connect([(n_mrcat, n_dwifslpreproc, [('out_file', 'se_epi_file')])]) # output of dwifslpreproc into 'preprocessedDWIs.mif' wf.connect([(n_dwifslpreproc, n_datasink, [('out_file', 'preprocessedDWIs.mif')])]) # DWI bias correct for B1 field inhomogeneity correction n_dwibiascorrect = Node( interface=preprocess.DWIBiasCorrect(use_ants=True), name='n_dwibiascorrect', ) # input preprocessed data wf.connect([(n_dwifslpreproc, n_dwibiascorrect, [('out_file', 'in_file')]) ]) # output biascorrect data into 'ANTSpreprocessedDWIs.mif' wf.connect([(n_dwibiascorrect, n_datasink, [('out_file', 'ANTSpreprocessedDWIs.mif')])]) # DWI2mask to compute whole brain mask from bias corrected data n_dwi2mask = Node(interface=mrt.BrainMask(out_file='mask.mif'), name='n_dwi2mask') wf.connect([(n_dwibiascorrect, n_dwi2mask, [('out_file', 'in_file')])]) wf.connect([(n_dwi2mask, n_datasink, [('out_file', 'mask.mif')])]) ################################################################################## ## 2) Fixel-based analysis # DWI2response for etimation of response function for spherical deconvolution n_dwi2response = Node(interface=mrt.ResponseSD(algorithm='dhollander', wm_file='wm_res.txt', gm_file='gm_res.txt', csf_file='csf_res.txt'), name='n_dwi2response') # input bias corrected data for response function estimation wf.connect([(n_dwibiascorrect, n_dwi2response, [('out_file', 'in_file')])]) # output WM, GM, CSF response text files wf.connect([(n_dwi2response, n_datasink, [('wm_file', 'wm_res.txt')])]) wf.connect([(n_dwi2response, n_datasink, [('gm_file', 'gm_res.txt')])]) wf.connect([(n_dwi2response, n_datasink, [('csf_file', 'csf_res.txt')])]) # DWI2fod for fibre orientation distribution estimation (FOD) n_dwi2fod = Node(interface=mrt.ConstrainedSphericalDeconvolution( algorithm='msmt_csd', wm_odf='wmfod.mif', gm_odf='gmfod.mif', csf_odf='csffod.mif'), name='n_dwi2fod') # utilise dwi2fod response files as input wf.connect([(n_dwibiascorrect, n_dwi2fod, [('out_file', 'in_file')])]) wf.connect([(n_dwi2response, n_dwi2fod, [('wm_file', 'wm_txt')])]) wf.connect([(n_dwi2response, n_dwi2fod, [('gm_file', 'gm_txt')])]) wf.connect([(n_dwi2response, n_dwi2fod, [('csf_file', 'csf_txt')])]) # output WM, GM and CSF FODs for saving wf.connect([(n_dwi2fod, n_datasink, [('wm_odf', 'wmfod.mif')])]) wf.connect([(n_dwi2fod, n_datasink, [('gm_odf', 'gmfod.mif')])]) wf.connect([(n_dwi2fod, n_datasink, [('csf_odf', 'csffod.mif')])]) # Mrconvert to select the first volume of the WM file (is best image out of 45 slices of wmfod file) n_mrconvert_fod = Node(interface=utils.MRConvert(out_file='Zwmfod.mif', coord=[3, 0]), name='n_mrconvert_fod') # utilise WM FOD as input wf.connect([(n_dwi2fod, n_mrconvert_fod, [('wm_odf', 'in_file')])]) # output z component of WM FOD wf.connect([(n_mrconvert_fod, n_datasink, [('out_file', 'Zwmfod.mif')])]) # MRcat to concatenate all WM, GM, CSF FOD files to see their distributions throughout Brain n_mrcat_fod = Node(interface=mrcatfunc.MRCat(out_file='vf.mif'), name='n_mrcat_fod') # connect Zwmfod, gmfod and csffod as inputs wf.connect([(n_mrconvert_fod, n_mrcat_fod, [('out_file', 'in_file1')])]) wf.connect([(n_dwi2fod, n_mrcat_fod, [('gm_odf', 'in_file2')])]) wf.connect([(n_dwi2fod, n_mrcat_fod, [('csf_odf', 'in_file3')])]) # output the mrcat file into file 'vf.mif' wf.connect([(n_mrcat_fod, n_datasink, [('out_file', 'vf.mif')])]) # fod2fixel # Perform segmentation of continuous FODs to produce discrete fixels # OUTPUTS: -afd afd.mif -peak peak.mif -disp disp.mif n_fod2fixel = Node( interface=fod2fixelfunc.fod2fixel( out_file='wmfixels', #afd_file = 'afd.mif', peak_file='peak.mif', disp_file='disp.mif'), name='n_fod2fixel') # let the peak value parameter be trialed as multiple values n_fod2fixel.iterables = ('fmls_peak_value', [0, 0.10, 0.50]) n_fod2fixel.iterables = ('fmls_integral', [0, 0.10, 0.50]) # obtain WM fibre image as input wf.connect([(n_dwi2fod, n_fod2fixel, [('wm_odf', 'in_file')])]) # ouputs of fod2fixel saved wf.connect([(n_fod2fixel, n_datasink, [('out_file', 'wmfixels')])]) wf.connect([(n_fod2fixel, n_datasink, [('afd_file', 'afd.mif')])]) wf.connect([(n_fod2fixel, n_datasink, [('peak_file', 'peak.mif')])]) wf.connect([(n_fod2fixel, n_datasink, [('disp_file', 'disp.mif')])]) # fixel2peaks to convert data in the fixel directory format into 4D image of 3-vectors n_fixel2peaks = Node(interface=fixel2peaksfunc.fixel2peaks( out_file='peaks_wmdirections.mif'), name='n_fixel2peaks') # look at multiple values for maximum number of fixels in each voxel n_fixel2peaks.iterables = ('number', [1, 2, 3]) # obtain directions file in output folder of fod2fixel, as input wf.connect([(n_fod2fixel, n_fixel2peaks, [('out_file', 'in_file')])]) # ouput of fixel2peaks saved in peaks_wmdirections.mif' wf.connect([(n_fixel2peaks, n_datasink, [('out_file', 'peaks_wmdirections.mif')])]) # MRmath to find normalised value of peak WM directions n_mrmath = Node(interface=mrt.MRMath( axis=3, operation='norm', out_file='norm_peaks_wmdirections.mif'), name='n_mrmath') # input peak fixel data wf.connect([(n_fixel2peaks, n_mrmath, [('out_file', 'in_file')])]) # output saved into 'norm_peaks_wmdirections.mif' wf.connect([(n_mrmath, n_datasink, [('out_file', 'norm_peaks_wmdirections.mif')])]) # MRcalc to divide peak WM direction by normalised value n_mrcalc = Node(interface=mrcalcfunc.MRCalc(operation='divide', out_file='wm_peak_dir.mif'), name='n_mrcalc') # fixel2peaks image as input 1 wf.connect([(n_fixel2peaks, n_mrcalc, [('out_file', 'in_file1')])]) # normalised fixel2peak image as input 2 wf.connect([(n_mrmath, n_mrcalc, [('out_file', 'in_file2')])]) # save output image as 'WM_peak_dir.mif' wf.connect([(n_mrcalc, n_datasink, [('out_file', 'WM_peak_dir.mif')])]) # MRconvert to extract Z component of peak directions n_mrconvert2 = Node(interface=utils.MRConvert( out_file='Zpeak_WM_Directions.mif', coord=[3, 2]), name='n_mrconvert2') # input normalised peak direction file wf.connect([(n_mrcalc, n_mrconvert2, [('out_file', 'in_file')])]) # save ouptut as 'Zpeak_WM_Directions.mif' wf.connect([(n_mrconvert2, n_datasink, [('out_file', 'Zpeak_WM_Directions.mif')])]) # MRcalc to find absolute value of peak fibre directions n_mrcalc2 = Node(interface=mrcalcfunc.MRCalc( operation='abs', out_file='absZpeak_WM_Directions.mif'), name='n_mrcalc2') # input z peaks image wf.connect([(n_mrconvert2, n_mrcalc2, [('out_file', 'in_file1')])]) # save output as 'absZpeak_WM_Directions.mif' wf.connect([(n_mrcalc2, n_datasink, [('out_file', 'absZpeak_WM_Directions.mif')])]) # MRcalc to get angle by doing inverse cosine n_mrcalc3 = Node(interface=mrcalcfunc.MRCalc( operation='acos', out_file='acosZpeak_WM_Directions.mif'), name='n_mrcalc3') # input normalised z component of peaks image wf.connect([(n_mrcalc2, n_mrcalc3, [('out_file', 'in_file1')])]) # save ouput as 'acosZpeak_WM_Directions.mif' wf.connect([(n_mrcalc3, n_datasink, [('out_file', 'acosZpeak_WM_Directions.mif')])]) # MRcalc to convert angle of peak fibre (w.r.t z axis), to degrees n_mrcalc4 = Node(interface=mrcalcfunc.MRCalc( operation='multiply', operand=180, out_file='Fixel1_Z_angle.mif'), name='n_mrcalc4') # input inverse cosine image of peak fibre wf.connect([(n_mrcalc3, n_mrcalc4, [('out_file', 'in_file1')])]) # output image as 'Fixel1_Z_angle.mif' wf.connect([(n_mrcalc4, n_datasink, [('out_file', 'Fixel1_Z_angle.mif')])]) # MRcalc to divide by pi to finish converting from radians to degrees n_mrcalc5 = Node(interface=mrcalcfunc.MRCalc( operation='divide', operand=3.14159265, out_file='Fixel1_Z_cos_deg.mif'), name='n_mrcalc5') # input image multiplied by 180 wf.connect([(n_mrcalc4, n_mrcalc5, [('out_file', 'in_file1')])]) # save output as 'Fixel1_Z_cos_deg.mif' wf.connect([(n_mrcalc5, n_datasink, [('out_file', 'Fixel1_Z_cos_deg.mif')]) ]) ################################################################################## ## 3) Tensor-based analysis # dwi2tensor to compute tensor from biascorrected DWI image n_dwi2tensor = Node(interface=mrt.FitTensor(out_file='dti.mif'), name='n_dwi2tensor') # input bias corrected image wf.connect([(n_dwibiascorrect, n_dwi2tensor, [('out_file', 'in_file')])]) # utilise mask to only compute tensors for regions of Brain wf.connect([(n_dwi2mask, n_dwi2tensor, [('out_file', 'in_mask')])]) # output data into 'dt.mif' wf.connect([(n_dwi2tensor, n_datasink, [('out_file', 'dt.mif')])]) # tensor2metric to convert tensors to generate maps of tensor-derived parameters n_tensor2metric = Node(interface=tensor2metricfunc.tensor2metric( modulate='none', num=1, vector_file='eigenvector.mif'), name='n_tensor2metric') # input tensor image wf.connect([(n_dwi2tensor, n_tensor2metric, [('out_file', 'input_file')])]) # save output eigenvectors of the diffusion tensor wf.connect([(n_tensor2metric, n_datasink, [('vector_file', 'eigenvector.mif')])]) # MRconvert to get eigenvector w.r.t z direction (main field) n_mrconvert3 = Node(interface=utils.MRConvert(coord=[3, 2], out_file='eigenvectorZ.mif'), name='n_mrconvert3') # input eigenvector file from tensor2metric wf.connect([(n_tensor2metric, n_mrconvert3, [('vector_file', 'in_file')])]) # save output as 'eigenvectorZ.mif' wf.connect([(n_mrconvert3, n_datasink, [('out_file', 'eigenvectorZ.mif')]) ]) # ALL SUBSEQUENT STEPS GET ANGLE IN DEGREES # MRcalc to find absolute value of z eigenvector file n_mrcalc6 = Node(interface=mrcalcfunc.MRCalc( operation='abs', out_file='abs_eigenvectorZ.mif'), name='n_mrcalc6') # z eigenvector image as input wf.connect([(n_mrconvert3, n_mrcalc6, [('out_file', 'in_file1')])]) # save output as 'abs_eigenvectorZ.mif' wf.connect([(n_mrcalc6, n_datasink, [('out_file', 'abs_eigenvectorZ.mif')]) ]) # MRcalc to get angle by doing inverse cosine n_mrcalc7 = Node(interface=mrcalcfunc.MRCalc( operation='acos', out_file='acos_eigenvectorZ.mif'), name='n_mrcalc7') # input absolute value of z eigenvector image wf.connect([(n_mrcalc6, n_mrcalc7, [('out_file', 'in_file1')])]) # save output as 'acos_eigenvectorZ.mif' wf.connect([(n_mrcalc7, n_datasink, [('out_file', 'acos_eigenvectorZ.mif') ])]) # MRcalc to convert angle to degrees n_mrcalc8 = Node( interface=mrcalcfunc.MRCalc(operation='multiply', operand=180, out_file='degrees_eigenvectorZ.mif'), name='n_mrcalc8') # input inverse cosine image of z eigenvector wf.connect([(n_mrcalc7, n_mrcalc8, [('out_file', 'in_file1')])]) # save output as 'degrees_eigenvectorZ.mif' wf.connect([(n_mrcalc8, n_datasink, [('out_file', 'degrees_eigenvectorZ.mif')])]) # MRcalc to divide by pi to finish converting from radians to degrees n_mrcalc9 = Node(interface=mrcalcfunc.MRCalc(operation='divide', operand=3.14159265, out_file='dti_z_cos_deg.mif'), name='n_mrcalc9') # input z eigenvector image multiplied by 180 wf.connect([(n_mrcalc8, n_mrcalc9, [('out_file', 'in_file1')])]) # save output as 'dti_z_cos_deg.mif' wf.connect([(n_mrcalc9, n_datasink, [('out_file', 'dti_z_cos_deg.mif')])]) # MRcalc to give difference image between fixel based and tensor based outputs n_mrcalc10 = Node(interface=mrcalcfunc.MRCalc( operation='subtract', out_file='diff_imag_tensor_minus_fixel.mif'), name='n_mrcalc10') # input tensor based image of whole Brain wf.connect([(n_mrcalc9, n_mrcalc10, [('out_file', 'in_file1')])]) # input fixel based image of Brain wf.connect([(n_mrcalc5, n_mrcalc10, [('out_file', 'in_file2')])]) # output difference image as 'diff_imag_tensor_minus_fixel.mif' wf.connect([(n_mrcalc10, n_datasink, [('out_file', 'diff_imag_tensor_minus_fixel.mif')])]) ##################################################################################### ## 4) Tensor based analysis on WM fibres only (NOT WHOLE BRAIN TENSORS) # MRthreshold to create WM mask from WM FOD (created earlier) n_mrthreshold = Node(interface=mrthresholdfunc.MRThreshold( out_file='thresholded_wmfod.mif'), name='n_mrthreshold') # input WM FOD wf.connect([(n_dwi2fod, n_mrthreshold, [('wm_odf', 'in_file')])]) # output thresholded WM FOD wf.connect([(n_mrthreshold, n_datasink, [('out_file', 'thresholded_wmfod.mif')])]) # MRconvert to extract 1st volume of thresholded WM FOD n_mrconvert4 = Node(interface=utils.MRConvert(coord=[3, 0], out_file='WMmask.mif'), name='n_mrconvert4') # input thresholded wmfod wf.connect([(n_mrthreshold, n_mrconvert4, [('out_file', 'in_file')])]) # save output as 'WMmask.mif' wf.connect([(n_mrconvert4, n_datasink, [('out_file', 'WMmask.mif')])]) # MRcalc to multiple WM mask with dti image to get tensors only of WM regions n_mrcalc11 = Node(interface=mrcalcfunc.MRCalc(operation='multiply', out_file='WM_dt.mif'), name='n_mrcalc11') # WM mask as input 1 wf.connect([(n_mrconvert4, n_mrcalc11, [('out_file', 'in_file1')])]) # dti image as input 2 wf.connect([(n_dwi2tensor, n_mrcalc11, [('out_file', 'in_file2')])]) # save output as 'WM_dt.mif' wf.connect([(n_mrcalc11, n_datasink, [('out_file', 'WM_dt.mif')])]) # tensor2metric to convert tensors to generate maps of tensor-derived parameters n_tensor2metric2 = Node(interface=tensor2metricfunc.tensor2metric( modulate='none', num=1, vector_file='WMeigenvector.mif'), name='n_tensor2metric2') # input tensor image wf.connect([(n_mrcalc11, n_tensor2metric2, [('out_file', 'input_file')])]) # save output eigenvectors of the diffusion tensor wf.connect([(n_tensor2metric2, n_datasink, [('vector_file', 'WMeigenvector.mif')])]) # MRconvert to get eigenvector w.r.t z direction (main field) n_mrconvert5 = Node(interface=utils.MRConvert( coord=[3, 2], out_file='WMeigenvectorZ.mif'), name='n_mrconvert5') # input eigenvector file from tensor2metric wf.connect([(n_tensor2metric2, n_mrconvert5, [('vector_file', 'in_file')]) ]) # save output as 'eigenvectorZ.mif' wf.connect([(n_mrconvert5, n_datasink, [('out_file', 'WMeigenvectorZ.mif') ])]) # ALL SUBSEQUENT STEPS GET ANGLE IN DEGREES # MRcalc to find absolute value of z eigenvector file n_mrcalc12 = Node(interface=mrcalcfunc.MRCalc( operation='abs', out_file='WM_abs_eigenvectorZ.mif'), name='n_mrcalc12') # z eigenvector image as input wf.connect([(n_mrconvert5, n_mrcalc12, [('out_file', 'in_file1')])]) # save output as 'WM_abs_eigenvectorZ.mif' wf.connect([(n_mrcalc12, n_datasink, [('out_file', 'WM_abs_eigenvectorZ.mif')])]) # MRcalc to get angle by doing inverse cosine n_mrcalc13 = Node(interface=mrcalcfunc.MRCalc( operation='acos', out_file='acos_WMeigenvectorZ.mif'), name='n_mrcalc13') # input absolute value of z eigenvector image wf.connect([(n_mrcalc12, n_mrcalc13, [('out_file', 'in_file1')])]) # save output as 'acos_WMeigenvectorZ.mif' wf.connect([(n_mrcalc13, n_datasink, [('out_file', 'acos_WMeigenvectorZ.mif')])]) # MRcalc to convert angle to degrees n_mrcalc14 = Node(interface=mrcalcfunc.MRCalc( operation='multiply', operand=180, out_file='degrees_WMeigenvectorZ.mif'), name='n_mrcalc14') # input inverse cosine image of WM z eigenvector wf.connect([(n_mrcalc13, n_mrcalc14, [('out_file', 'in_file1')])]) # save output as 'degrees_WMeigenvectorZ.mif' wf.connect([(n_mrcalc14, n_datasink, [('out_file', 'degrees_WMeigenvectorZ.mif')])]) # MRcalc to divide by pi to finish converting from radians to degrees n_mrcalc15 = Node( interface=mrcalcfunc.MRCalc(operation='divide', operand=3.14159265, out_file='WMdti_z_cos_deg.mif'), name='n_mrcalc15') # input WM z eigenvector image multiplied by 180 wf.connect([(n_mrcalc14, n_mrcalc15, [('out_file', 'in_file1')])]) # save output as 'WMdti_z_cos_deg.mif' wf.connect([(n_mrcalc15, n_datasink, [('out_file', 'WMdti_z_cos_deg.mif')]) ]) # MRcalc to give difference image between fixel based and WM tensor based outputs n_mrcalc16 = Node(interface=mrcalcfunc.MRCalc( operation='subtract', out_file='diffImage_WMtensor_minus_fixel.mif'), name='n_mrcalc16') # input fixel image of Brain wf.connect([(n_mrcalc15, n_mrcalc16, [('out_file', 'in_file1')])]) # input tensor image of WM fibres of Brain wf.connect([(n_mrcalc5, n_mrcalc16, [('out_file', 'in_file2')])]) # output difference image as 'diff_imag_WMtensor_minus_fixel.mif' wf.connect([(n_mrcalc16, n_datasink, [('out_file', 'diffImage_WMtensor_minus_fixel.mif')])]) ###################################################################################### return wf