Пример #1
0
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
Пример #2
0
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
Пример #3
0
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
Пример #4
0
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
Пример #5
0
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