Esempio n. 1
0
 def init_params(self, dwi_file: str, mask: str, out_file: str, out_noise: str):
     denoise = mrt.DWIDenoise()
     denoise.inputs.in_file = dwi_file
     denoise.inputs.mask = mask
     denoise.inputs.out_file = out_file
     denoise.inputs.noise = out_noise
     return denoise
Esempio n. 2
0
def denoise_dwi(in_file: Path, in_mask: Path, out_file: Path):
    denoise = mrt.DWIDenoise()
    denoise.inputs.in_file = in_file
    denoise.inputs.noise = in_file.with_name(in_file.stem + "_noise.mif")
    denoise.inputs.mask = in_mask
    denoise.inputs.out_file = out_file
    print(denoise.cmdline)
    return denoise
def Denoise(in_file, in_mask, out_file):
    denoise = mrt.DWIDenoise()
    denoise.inputs.in_file = in_file
    denoise.inputs.noise = in_file.replace(".mif", "_noise.mif")
    denoise.inputs.mask = in_mask
    denoise.inputs.out_file = out_file
    print(denoise.cmdline)
    denoise.run()
    return out_file
Esempio n. 4
0
    def preprocess_dwi_data(self,
                            data,
                            index,
                            acqp,
                            atlas2use,
                            ResponseSD_algorithm='tournier',
                            fod_algorithm='csd',
                            tract_algorithm='iFOD2',
                            streamlines_number='10M'):
        '''
        preprocessing of dwi data and connectome extraction

        Parameters
        ----------

        subjects_dir = path to the subjects' folders
        data: tuple |
            a tuple having the path to dwi, bvecs and bvals files. It is obtained
            using the function grab_data()
        index: str |
            Name of text file specifying the relationship between the images in
            --imain and the information in --acqp and --topup. E.g. index.txt
        acqp: str |
            Name of text file with information about the acquisition of the images
            in --imain
        atlas2use: str |
             The input node parcellation image
        ResponseSD_algorithm (optional): str |
             Select the algorithm to be used to complete the script operation;
             Options are: dhollander, fa, manual, msmt_5tt, tax, tournier
             (Default is 'tournier')
        fod_algorithm (optional): str |
             The algorithm to use for FOD estimation. (options are: csd,msmt_csd)
             (Default is 'csd')
        tract_algorithm (optional): str |
            specify the tractography algorithm to use. Valid choices are: FACT,
            iFOD1, iFOD2, Nulldist1, Nulldist2, SD_Stream, Seedtest, Tensor_Det,
            Tensor_Prob (Default is 'iFOD2')
        streamlines_number (optional): str |
            set the desired number of streamlines (Default is '10M')
    '''

        if len(data[0]) != len(data[1]):
            raise ValueError(
                'dwi datas do not have the same shape of bvec files')
        if len(data[0]) != len(data[2]):
            raise ValueError(
                'dwi datas do not have the same shape of bval files')
        if len(data[1]) != len(data[2]):
            raise ValueError(
                'bvec files do not have the same shape of bvec files')

        for subj in range(len(data[0])):
            print('Extracting B0 volume for subject', subj)
            self.roi = fsl.ExtractROI(
                in_file=data[0][subj],
                roi_file=os.path.join(
                    os.path.split(data[0][subj])[0] + '/' +
                    os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                    '_nodiff.nii.gz'),
                t_min=0,
                t_size=1)
            self.roi.run()

            print('Converting into .mif for subject', subj)
            self.mrconvert = mrt.MRConvert()
            self.mrconvert.inputs.in_file = data[0][subj]
            self.mrconvert.inputs.grad_fsl = (data[1][subj], data[2][subj])
            self.mrconvert.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] + '_dwi.mif')
            self.mrconvert.run()

            print('Denoising data for subject', subj)
            self.denoise = mrt.DWIDenoise()
            self.denoise.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] + '_dwi.mif')
            self.denoise.inputs.noise = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_noise.mif')
            self.denoise.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised.mif')
            self.denoise.run()

            self.denoise_convert = mrt.MRConvert()
            self.denoise_convert.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised.mif')
            self.denoise_convert.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised.nii.gz')
            self.denoise_convert.run()

            print('Skull stripping for subject', subj)
            self.mybet = fsl.BET()
            self.mybet.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_nodiff.nii.gz')
            self.mybet.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_denoised_brain.nii.gz')
            self.mybet.inputs.frac = 0.1
            self.mybet.inputs.robust = True
            self.mybet.inputs.mask = True
            self.mybet.run()

            print('Running Eddy for subject', subj)
            self.eddy = Eddy()
            self.eddy.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised.nii.gz')
            self.eddy.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_denoised_brain_mask.nii.gz')
            self.eddy.inputs.in_acqp = acqp
            self.eddy.inputs.in_bvec = data[1][subj]
            self.eddy.inputs.in_bval = data[2][subj]
            self.eddy.inputs.out_base = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy.nii.gz')
            self.eddy.run()

            print('Running Bias Correction for subject', subj)
            self.bias_correct = mrt.DWIBiasCorrect()
            self.bias_correct.inputs.use_ants = True
            self.bias_correct.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy.nii.gz')
            self.bias_correct.inputs.grad_fsl = (os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy.eddy_rotated_bvecs.bvec'), data[2][subj])
            self.bias_correct.inputs.bias = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] + '_bias.mif')
            self.bias_correct.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy_unbiased.mif')
            self.bias_correct.run()

            print('Calculating Response function for subject', subj)
            self.resp = mrt.ResponseSD()
            self.resp.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy_unbiased.mif')
            self.resp.inputs.algorithm = ResponseSD_algorithm
            self.resp.inputs.grad_fsl = (os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy.eddy_rotated_bvecs.bvec'), data[2][subj])
            self.resp.inputs.wm_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_response.txt')
            self.resp.run()

            print('Estimating FOD for subject', subj)
            self.fod = mrt.EstimateFOD()
            self.fod.inputs.algorithm = fod_algorithm
            self.fod.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_dwi_denoised_eddy_unbiased.mif')
            self.fod.inputs.wm_txt = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_response.txt')
            self.fod.inputs.mask_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_denoised_brain_mask.nii.gz')
            self.fod.inputs.grad_fsl = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_response.txt')
            self.fod.run()

            print('Extracting whole brain tract for subject', subj)
            self.tk = mrt.Tractography()
            self.tk.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] + 'fods.mif')
            self.tk.inputs.roi_mask = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_denoised_brain_mask.nii.gz')
            self.tk.inputs.algorithm = tract_algorithm
            self.tk.inputs.seed_image = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_denoised_brain_mask.nii.gz')
            self.tk.inputs.select = streamlines_number
            self.tk.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_whole_brain_' + streamlines_number + '.tck')
            self.tk.run()

            print('Extracting connectome for subject', subj)
            self.mat = mrt.BuildConnectome()
            self.mat.inputs.in_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_whole_brain_' + streamlines_number + '.tck')
            self.mat.inputs.in_parc = atlas2use
            self.mat.inputs.out_file = os.path.join(
                os.path.split(data[0][subj])[0] + '/' +
                os.path.split(data[0][0])[1].split(".nii.gz")[0] +
                '_connectome.csv')
            self.mat.run()
import os 
from os.path import abspath
from datetime import datetime
from IPython.display import Image
import pydot
from nipype import Workflow, Node, MapNode, Function, config
from nipype.interfaces.fsl import TOPUP, ApplyTOPUP, BET, ExtractROI,  Eddy, FLIRT, FUGUE
from nipype.interfaces.fsl.maths import MathsCommand
import nipype.interfaces.utility as util 
import nipype.interfaces.mrtrix3 as mrt
#Requirements for the workflow to run smoothly: All files as in NIfTI-format and named according to the following standard: 
#Images are from the tonotopy DKI sequences on the 7T Philips Achieva scanner in Lund. It should work with any DKI sequence and possibly also a standard DTI but the setting for B0-corrections, epi-distortion corrections and eddy current corrections will be wrong. 
#DKI file has a base name shared with bvec and bval in FSL format. E.g. "DKI.nii.gz" "DKI.bvec" and "DKI.bval". 
#There is one b0-volume with reversed (P->A) phase encoding called DKIbase+_revenc. E.g. "DKI_revenc.nii.gz". 
#Philips B0-map magnitude and phase offset (in Hz) images. 
#One input file for topup describing the images as specified by topup. 
#Set nbrOfThreads to number of available CPU threads to run the analyses. 
### Need to make better revenc for the 15 version if we choose to use it (i.e. same TE and TR)
#Set to relevant directory/parameters
datadir=os.path.abspath("/Users/ling-men/Documents/MRData/testDKI")
rawDKI_base='DKI_15' 
B0map_base = 'B0map'
nbrOfThreads=6
print_graph = True 
acqparam_file = os.path.join(datadir,'acqparams.txt')
index_file = os.path.join(datadir,'index.txt')
####
#config.enable_debug_mode()
DKI_nii=os.path.join(datadir, rawDKI_base+'.nii.gz')
DKI_bval=os.path.join(datadir, rawDKI_base+'.bval')
Esempio n. 6
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
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