def main(self):

        # if self.force:
        #     logging.info('Deleting previous output directory')
        #     rm('-rf', self.outDir)


        temp= self.dwi_file.split(',')
        primaryVol= abspath(temp[0])
        if len(temp)<2:
            raise AttributeError('Two volumes are required for --imain')
        else:
            secondaryVol= abspath(temp[1])

        if self.b0_brain_mask:
            temp = self.b0_brain_mask.split(',')
            primaryMask = abspath(temp[0])
            if len(temp) == 2:
                secondaryMask = abspath(temp[1])
            else:
                secondaryMask = abspath(temp[0])

        else:
            primaryMask=[]
            secondaryMask=[]


        # obtain 4D/3D info and time axis info
        dimension = load_nifti(primaryVol).header['dim']
        dim1 = dimension[0]
        if dim1!=4:
            raise AttributeError('primary volume must be 4D, however, secondary can be 3D/4D')
        numVol1 = dimension[4]

        dimension = load_nifti(secondaryVol).header['dim']
        dim2 = dimension[0]
        numVol2 = dimension[4]


        temp= self.bvals_file.split(',')
        if len(temp)>=1:
            primaryBval= abspath(temp[0])
        if len(temp)==2:
            secondaryBval= abspath(temp[1])
        elif len(temp)==1 and dim2==4:
            secondaryBval= primaryBval
        elif len(temp)==1 and dim2==3:
            secondaryBval=[]
        elif len(temp)==0:
            raise AttributeError('--bvals are required')

        temp= self.bvecs_file.split(',')
        if len(temp)>=1:
            primaryBvec= abspath(temp[0])
        if len(temp)==2:
            secondaryBvec= abspath(temp[1])
        elif len(temp) == 1 and dim2 == 4:
            secondaryBvec = primaryBvec
        elif len(temp)==1 and dim2==3:
            secondaryBvec=[]
        else:
            raise AttributeError('--bvecs are required')



        with TemporaryDirectory() as tmpdir:

            tmpdir= local.path(tmpdir)

            # mask both volumes, fslmaths can do that irrespective of dimension
            logging.info('Masking the volumes')

            primaryMaskedVol = tmpdir / 'primaryMasked.nii.gz'
            secondaryMaskedVol = tmpdir / 'secondaryMasked.nii.gz'

            if primaryMask:
                # mask the volume
                fslmaths[primaryVol, '-mas', primaryMask, primaryMaskedVol] & FG
            else:
                primaryMaskedVol= primaryVol

            if secondaryMask:
                # mask the volume
                fslmaths[secondaryVol, '-mas', secondaryMask, secondaryMaskedVol] & FG
            else:
                secondaryMaskedVol= secondaryVol


            logging.info('Extracting B0 from masked volumes')
            B0_PA= tmpdir / 'B0_PA.nii.gz'
            B0_AP= tmpdir / 'B0_AP.nii.gz'

            obtainB0(primaryMaskedVol, primaryBval, B0_PA, self.num_b0)

            if dim2==4:
                obtainB0(secondaryMaskedVol, secondaryBval, B0_AP, self.num_b0)
            else:
                B0_AP= secondaryMaskedVol


            B0_PA_AP_merged = tmpdir / 'B0_PA_AP_merged.nii.gz'
            with open(self.acqparams_file._path) as f:
                acqp= f.read().split('\n')

            logging.info('Writing acqparams.txt for topup')

            # firstDim: first acqp line should be replicated this number of times
            firstB0dim= load_nifti(str(B0_PA)).header['dim'][4]
            # secondDim: second acqp line should be replicated this number of times
            secondB0dim= load_nifti(str(B0_AP)).header['dim'][4]
            acqp_topup= tmpdir / 'acqp_topup.txt'
            with open(acqp_topup,'w') as f:
                for i in range(firstB0dim):
                    f.write(acqp[0]+'\n')

                for i in range(secondB0dim):
                    f.write(acqp[1]+'\n')


            logging.info('Merging B0_PA and BO_AP')
            fslmerge('-t', B0_PA_AP_merged, B0_PA, B0_AP)


            topup_params, applytopup_params, eddy_openmp_params= obtain_fsl_eddy_params(self.eddy_config_file._path)

            # Example for topup
            # === on merged b0 images ===
            # https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide#Running_topup_on_the_b.3D0_volumes
            # topup --imain=both_b0 --datain=my_acq_param.txt --out=my_topup_results
            # === on all b0 images ===
            # https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#Running_topup
            # topup --imain=all_my_b0_images.nii --datain=acquisition_parameters.txt --config=b02b0.cnf --out=my_output


            logging.info('Running topup')
            topup_results= tmpdir / 'topup_results'
            topup[f'--imain={B0_PA_AP_merged}',
                  f'--datain={acqp_topup}',
                  f'--out={topup_results}',
                  '--verbose',
                  topup_params.split()] & FG



            logging.info('Running applytopup')
            
            topupMask= tmpdir / 'topup_mask.nii.gz'

            # applytopup on primary4D,secondary4D/3D
            topupOut= tmpdir / 'topup_out.nii.gz'
            if dim2==4:
                applytopup[f'--imain={primaryMaskedVol},{secondaryMaskedVol}',
                           f'--datain={self.acqparams_file}',
                           '--inindex=1,2',
                           f'--topup={topup_results}',
                           f'--out={topupOut}',
                           '--verbose',
                           applytopup_params.split()] & FG

            else:
                applytopup[f'--imain={B0_PA},{B0_AP}',
                           f'--datain={self.acqparams_file}',
                           '--inindex=1,2',
                           f'--topup={topup_results}',
                           f'--out={topupOut}',
                           '--verbose',
                           applytopup_params.split()] & FG


            topupOutMean= tmpdir / 'topup_out_mean.nii.gz'
            fslmaths[topupOut, '-Tmean', topupOutMean] & FG
            bet[topupOutMean, topupMask._path.split('_mask.nii.gz')[0], '-m', '-n'] & FG


            # another approach could be
            # threshold mean of primary,secondary mask at 0.5 and obtain modified mask, use that mask for eddy_openmp
            # fslmerge[topupMask, '-t', primaryMask, secondaryMask] & FG
            # fslmaths[topupMask, '-Tmean', topupMask] & FG
            # fslmaths[topupMask, '-thr', '0.5', topupMask, '-odt' 'char'] & FG

            logging.info('Writing index.txt for topup')
            indexFile= tmpdir / 'index.txt'
            with open(indexFile, 'w') as f:
                for i in range(numVol1):
                    f.write('1\n')


            outPrefix = tmpdir / basename(primaryVol).split('.')[0] + '_Ep_Ed'

            temp = self.whichVol.split(',')
            if len(temp)==1 and temp[0]=='1':
                # correct only primary4D volume

                eddy_openmp[f'--imain={primaryMaskedVol}',
                            f'--mask={topupMask}',
                            f'--acqp={self.acqparams_file}',
                            f'--index={indexFile}',
                            f'--bvecs={primaryBvec}',
                            f'--bvals={primaryBval}',
                            f'--out={outPrefix}',
                            f'--topup={topup_results}',
                            '--verbose',
                            eddy_openmp_params.split()] & FG




            elif len(temp)==2 and temp[1]=='2':
                # sylvain would like to correct both primary and secondary volumes


                with open(indexFile, 'a') as f:
                    for i in range(numVol2):
                        f.write('2\n')


                # join both bvalFiles
                bvals1= read_bvals(primaryBval)
                if dim2==4 and not secondaryBval:
                    bvals2= bvals1.copy()
                elif dim2==4 and secondaryBval:
                    bvals2= read_bvals(secondaryBval)
                elif dim2==3:
                    bvals2=[0]

                combinedBvals = tmpdir / 'combinedBvals.txt'
                write_bvals(combinedBvals, bvals1+bvals2)

                # join both bvecFiles
                bvecs1= read_bvecs(primaryBvec)
                if dim2==4 and not secondaryBvec:
                    bvecs2= bvecs1.copy()
                elif dim2==4 and secondaryBvec:
                    bvecs2= read_bvecs(secondaryBvec)
                elif dim2==3:
                    bvecs2=[[0,0,0]]

                # join both bvecFiles
                combinedBvecs = tmpdir / 'combinedBvecs.txt'
                write_bvecs(combinedBvecs, bvecs1+bvecs2)

                combinedData= tmpdir / 'combinedData.nii.gz'
                fslmerge('-t', combinedData, primaryMaskedVol, secondaryMaskedVol)

                eddy_openmp[f'--imain={combinedData}',
                            f'--mask={topupMask}',
                            f'--acqp={self.acqparams_file}',
                            f'--index={indexFile}',
                            f'--bvecs={combinedBvecs}',
                            f'--bvals={combinedBvals}',
                            f'--out={outPrefix}',
                            f'--topup={topup_results}',
                            '--verbose',
                            eddy_openmp_params.split()] & FG


            else:
                raise ValueError('Invalid --whichVol')


            # copy bval,bvec to have same prefix as that of eddy corrected volume
            copyfile(outPrefix+'.eddy_rotated_bvecs', outPrefix+'.bvec')
            copyfile(primaryBval, outPrefix+'.bval')
            
            # rename topupMask to have same prefix as that of eddy corrected volume
            topupMask.move(outPrefix+'_mask.nii.gz')

            tmpdir.move(self.outDir)
示例#2
0
文件: eddy.py 项目: pnlbwh/pnlpipe
    def main(self):
        self.out = local.path(self.out)
        if self.out.exists():
            if self.overwrite:
                self.out.delete()
            else:
                logging.error(
                    "{} exists, use '--force' to overwrite it".format(
                        self.out))
                sys.exit(1)
        outxfms = self.out.dirname / self.out.stem + '-xfms.tgz'
        with TemporaryDirectory() as tmpdir, local.cwd(tmpdir):
            tmpdir = local.path(tmpdir)

            # fileinput() caused trouble reading data file in python 3, so switching to nrrd
            # if the hdr has 'nan' in space origin, the following will take care of that
            img = nrrd.read(self.dwi)
            dwi = img[0]
            hdr = img[1]

            hdr_out = hdr.copy()
            hdr_out['space origin'] = hdr_out['space origin'][0:3]

            nrrd.write('dwijoined.nhdr',
                       dwi,
                       header=hdr_out,
                       compression_level=1)

            # we want to use this hdr to write a new .nhdr file with corresponding data file
            # so delete old data file from the hdr
            if 'data file' in hdr_out.keys():
                del hdr_out['data file']
            elif 'datafile' in hdr_out.keys():
                del hdr_out['datafile']

            if 'content' in hdr_out.keys():
                del hdr_out['content']

            logging.info('Dice the DWI')

            # Since fslmerge works along the 3rd axis only, dicing also has to be along that axis
            # So, use `unu permute` to reorient the volumes to be stacked along 3rd axis only
            # Include this issue in the tutorial
            (unu['convert', '-t', 'int16', '-i', 'dwijoined.nhdr']
             | unu['dice', '-a', '3', '-o', 'Diffusion-G'])()
            vols = tmpdir.glob('Diffusion-G*.nrrd')
            vols.sort()

            logging.info('Extract the B0')
            bse_py('-i', 'dwijoined.nhdr', '-o', 'b0.nrrd')
            ConvertBetweenFileFormats('b0.nrrd', 'b0.nii.gz', 'short')

            logging.info('Register each volume to the B0')

            # use the following multi-processed loop
            pool = Pool(int(self.nproc))
            res = pool.map_async(_Register_vol, vols)
            volsRegistered = res.get()
            pool.close()
            pool.join()

            # or use the following for loop
            # volsRegistered = []
            # for vol in vols:
            #     volnii = vol.with_suffix('.nii.gz')
            #     ConvertBetweenFileFormats(vol, volnii, 'short')
            #     logging.info('Run FSL flirt affine registration')
            #     flirt('-interp' ,'sinc'
            #           ,'-sincwidth' ,'7'
            #           ,'-sincwindow' ,'blackman'
            #           ,'-in', volnii
            #           ,'-ref', 'b0.nii.gz'
            #           ,'-nosearch'
            #           ,'-o', volnii
            #           ,'-omat', volnii.with_suffix('.txt', depth=2)
            #           ,'-paddingsize', '1')
            #     volsRegistered.append(volnii)

            fslmerge('-t', 'EddyCorrect-DWI', volsRegistered)
            transforms = tmpdir.glob('Diffusion-G*.txt')
            transforms.sort()

            # nibabel loading can be avoided by setting 'data file' = EddyCorrect-DWI.nii.gz
            # and 'byteskip' = -1
            # Tashrif updated Pynrrd package to properly handle that
            new_dwi = nib.load('EddyCorrect-DWI.nii.gz').get_data()

            logging.info('Extract the rotations and realign the gradients')

            space = hdr_out['space'].lower()
            if (space == 'left'):
                spctoras = np.matrix([[-1, 0, 0], [0, -1, 0], [0, 0, 1]])
            else:
                spctoras = np.matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
            mf = np.matrix(hdr['measurement frame'])

            # Transforms are in RAS so need to do inv(MF)*inv(SPC2RAS)*ROTATION*SPC2RAS*MF*GRADIENT
            mfras = mf.I * spctoras.I
            rasmf = spctoras * mf
            for (i, t) in enumerate(transforms):

                gDir = [
                    float(num) for num in hdr_out['DWMRI_gradient_' +
                                                  '{:04}'.format(i)].split(' ')
                    if num
                ]

                logging.info('Apply ' + t)
                tra = np.loadtxt(t)
                # removes the translation
                aff = np.matrix(tra[0:3, 0:3])
                # computes the finite strain of aff to get the rotation
                rot = aff * aff.T
                # compute the square root of rot
                [el, ev] = np.linalg.eig(rot)
                eL = np.identity(3) * np.sqrt(el)
                sq = ev * eL * ev.I
                # finally the rotation is defined as
                rot = sq.I * aff
                newdir = np.dot(mfras * rot * rasmf, gDir)

                hdr_out['DWMRI_gradient_' + '{:04}'.format(i)] = ('   ').join(
                    str(x) for x in newdir.tolist()[0])

            tar('cvzf', outxfms, transforms)

            nrrd.write(self.out, new_dwi, header=hdr_out, compression_level=1)

            if self.debug:
                tmpdir.copy(
                    join(dirname(self.out), "eddy-debug-" + str(getpid())))
示例#3
0
    def main(self):
        self.out = local.path(self.out)
        if self.out.exists():
            if self.overwrite:
                self.out.delete()
            else:
                logging.error(
                    "{} exists, use '--force' to overwrite it".format(
                        self.out))
                sys.exit(1)
        outxfms = self.out.dirname / self.out.stem + '-xfms.tgz'
        with TemporaryDirectory() as tmpdir, local.cwd(tmpdir):
            tmpdir = local.path(tmpdir)

            unu('save', '-f', 'nrrd', '-e', 'gzip', '-i', self.dwi, '-o',
                'dwijoined.nhdr')

            logging.info('Dice the DWI')
            (unu['convert', '-t', 'int16', '-i', 'dwijoined.nhdr']
             | unu['dice', '-a', '3', '-o', 'Diffusion-G'])()
            vols = tmpdir.glob('Diffusion-G*.nrrd')
            vols.sort()

            logging.info('Extract the B0')
            bse_py('-i', 'dwijoined.nhdr', '-o', 'b0.nrrd')
            ConvertBetweenFileFormats('b0.nrrd', 'b0.nii.gz', 'short')

            logging.info('Register each volume to the B0')
            volsRegistered = []
            for vol in vols:
                volnii = vol.with_suffix('.nii.gz')
                ConvertBetweenFileFormats(vol, volnii, 'short')
                logging.info('Run FSL flirt affine registration')
                flirt('-interp', 'sinc', '-sincwidth', '7', '-sincwindow',
                      'blackman', '-in', volnii, '-ref', 'b0.nii.gz',
                      '-nosearch', '-o', volnii, '-omat',
                      volnii.with_suffix('.txt', depth=2), '-paddingsize', '1')
                volsRegistered.append(volnii)
            fslmerge('-t', 'EddyCorrect-DWI', volsRegistered)
            transforms = tmpdir.glob('Diffusion-G*.txt')
            transforms.sort()

            logging.info('Extract the rotations and realign the gradients')
            gDir = []
            header = ''
            gNum = []
            gframe = []
            with open('dwijoined.nhdr') as f:
                for line in f:
                    if line.find('DWMRI_gradient_') != -1:
                        gNum.append(line[15:19])
                        gDir.append(map(float, line[21:-1].split()))
                    elif line.find('data file:') != -1:
                        header = header + 'data file: EddyCorrect-DWI.nii.gz\n'
                    elif line.find('encoding:') != -1:
                        header = header + line + 'byteskip: -1\n'
                    elif line.find('measurement frame:') != -1:
                        header = header + line
                        mf = np.matrix([
                            map(float,
                                line.split()[2][1:-1].split(',')),
                            map(float,
                                line.split()[3][1:-1].split(',')),
                            map(float,
                                line.split()[4][1:-1].split(','))
                        ])
                    elif line.find('space:') != -1:
                        header = header + line
                        # Here I assume either lps or ras so only need to check the first letter
                        space = line.split()[1][0]
                        if (space == 'l') | (space == 'L'):
                            spctoras = np.matrix([[-1, 0, 0], [0, -1, 0],
                                                  [0, 0, 1]])
                        else:
                            spctoras = np.matrix([[1, 0, 0], [0, 1, 0],
                                                  [0, 0, 1]])
                    else:
                        header = header + line

            with open('EddyCorrect-DWI.nhdr', 'w') as f:
                f.write(header)
                i = 0
                # Transforms are in RAS so need to do inv(MF)*inv(SPC2RAS)*ROTATION*SPC2RAS*MF*GRADIENT
                mfras = mf.I * spctoras.I
                rasmf = spctoras * mf
                for t in transforms:
                    logging.info('Apply ' + t)
                    tra = np.loadtxt(t)
                    #removes the translation
                    aff = np.matrix(tra[0:3, 0:3])
                    # computes the finite strain of aff to get the rotation
                    rot = aff * aff.T
                    # Computer the square root of rot
                    [el, ev] = np.linalg.eig(rot)
                    eL = np.identity(3) * np.sqrt(el)
                    sq = ev * eL * ev.I
                    # finally the rotation is defined as
                    rot = sq.I * aff
                    newdir = np.dot(mfras * rot * rasmf, gDir[i])
                    f.write('DWMRI_gradient_' + gNum[i] + ':= ' +
                            str(newdir[0, 0]) + ' ' + str(newdir[0, 1]) + ' ' +
                            str(newdir[0, 2]) + '\n')
                    i = i + 1

            tar('cvzf', outxfms, transforms)
            unu('save', '-f', 'nrrd', '-e', 'gzip', '-i',
                'EddyCorrect-DWI.nhdr', '-o', self.out)

            if self.debug:
                tmpdir.move("eddy-debug-" + str(getpid()))
示例#4
0
    def main(self):
        self.out = local.path(self.out)
        if self.out.exists():
            if self.overwrite:
                self.out.delete()
            else:
                logging.error("{} exists, use '--force' to overwrite it".format(self.out))
                sys.exit(1)

        outxfms = self.out.dirname / self.out.stem+'_xfms.tgz'

        with TemporaryDirectory() as tmpdir, local.cwd(tmpdir):
            tmpdir = local.path(tmpdir)

            dicePrefix = 'vol'

            logging.info('Dice the DWI')
            fslsplit[self.dwi] & FG

            logging.info('Extract the B0')
            check_call((' ').join([pjoin(FILEDIR,'bse.py'), '-i', self.dwi._path, '-o', 'b0.nii.gz']), shell= True)

            logging.info('Register each volume to the B0')
            vols = sorted(tmpdir // (dicePrefix + '*.nii.gz'))

            # use the following multi-processed loop
            pool= Pool(int(self.nproc))
            res= pool.map_async(_Register_vol, vols)
            volsRegistered= res.get()
            pool.close()
            pool.join()

            # or use the following for loop
            # volsRegistered = []
            # for vol in vols:
            #     volnii = vol.with_suffix('.nii.gz')
            #     logging.info('Run FSL flirt affine registration')
            #     flirt('-interp' ,'sinc'
            #           ,'-sincwidth' ,'7'
            #           ,'-sincwindow' ,'blackman'
            #           ,'-in', volnii
            #           ,'-ref', 'b0.nii.gz'
            #           ,'-nosearch'
            #           ,'-o', volnii
            #           ,'-omat', volnii.with_suffix('.txt', depth=2)
            #           ,'-paddingsize', '1')
            #     volsRegistered.append(volnii)


            fslmerge('-t', 'EddyCorrect-DWI.nii.gz', volsRegistered)
            transforms = tmpdir.glob(dicePrefix+'*.txt')
            transforms.sort()


            logging.info('Extract the rotations and realign the gradients')

            bvecs= read_bvecs(self.bvecFile._path)
            bvecs_new= bvecs.copy()
            for (i,t) in enumerate(transforms):

                logging.info('Apply ' + t)
                tra = np.loadtxt(t)

                # removes the translation
                aff = np.matrix(tra[0:3,0:3])

                # computes the finite strain of aff to get the rotation
                rot = aff*aff.T

                # compute the square root of rot
                [el, ev] = np.linalg.eig(rot)
                eL = np.identity(3)*np.sqrt(el)
                sq = ev*eL*ev.I

                # finally the rotation is defined as
                rot = sq.I*aff

                bvecs_new[i] = np.dot(rot,bvecs[i]).tolist()[0]



            tar('cvzf', outxfms, transforms)

            # save modified bvecs
            write_bvecs(self.out._path+'.bvec', bvecs_new)

            # save EddyCorrect-DWI
            local.path('EddyCorrect-DWI.nii.gz').copy(self.out._path+'.nii.gz')

            # copy bvals
            self.bvalFile.copy(self.out._path+'.bval')

            if self.debug:
                tmpdir.copy(pjoin(dirname(self.out),"eddy-debug-"+str(getpid())))
示例#5
0
    def main(self):

        from plumbum.cmd import eddy_openmp

        # cli.NonexistentPath is already making sure it does not exist
        self.outDir.mkdir()

        if self.useGpu:
            try:
                from plumbum.cmd import nvcc
                nvcc['--version'] & FG

                print('\nCUDA found, looking for available GPU\n')
                from GPUtil import getFirstAvailable
                getFirstAvailable()

                print('available GPU found, looking for eddy_cuda executable\n'
                      'make sure you have created a softlink according to '
                      'https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide')
                from plumbum.cmd import eddy_cuda as eddy_openmp

                print('\neddy_cuda executable found\n')
            except:
                print('nvcc, available GPU, and/or eddy_cuda was not found, using eddy_openmp')



        def _eddy_openmp(modData, modBvals, modBvecs, eddy_openmp_params):
            
            print('eddy_openmp/cuda parameters')
            print(eddy_openmp_params)
            print('')
            
            # eddy_openmp yields as many volumes as there are input volumes
            # this is the main output and consists of the input data after correction for
            # eddy currents, subject movement, and susceptibility if --topup was specified
            
            eddy_openmp[f'--imain={modData}',
                        f'--mask={topupMask}',
                        f'--acqp={self.acqparams_file}',
                        f'--index={indexFile}',
                        f'--bvecs={modBvecs}',
                        f'--bvals={modBvals}',
                        f'--out={outPrefix}',
                        f'--topup={topup_results}',
                        eddy_openmp_params.split()] & FG


            # free space, see https://github.com/pnlbwh/pnlNipype/issues/82
            if '--repol' in eddy_openmp_params:
                rm[f'{outPrefix}.eddy_outlier_free_data.nii.gz'] & FG
                    
            bvals = np.array(read_bvals(modBvals))
            ind= [i for i in range(len(bvals)) if bvals[i]>B0_THRESHOLD and bvals[i]<= REPOL_BSHELL_GREATER]

            if '--repol' in eddy_openmp_params and len(ind):

                print('\nDoing eddy_openmp/cuda again without --repol option '
                      f'to obtain eddy correction w/o outlier replacement for b<={REPOL_BSHELL_GREATER} shells\n')

                eddy_openmp_params = eddy_openmp_params.split()
                eddy_openmp_params.remove('--repol')
                print(eddy_openmp_params)
                print('')
                wo_repol_outDir = local.path(outPrefix).dirname.join('wo_repol')
                wo_repol_outDir.mkdir()
                wo_repol_outPrefix = pjoin(wo_repol_outDir, basename(outPrefix))


                eddy_openmp[f'--imain={modData}',
                            f'--mask={topupMask}',
                            f'--acqp={self.acqparams_file}',
                            f'--index={indexFile}',
                            f'--bvecs={modBvecs}',
                            f'--bvals={modBvals}',
                            f'--out={wo_repol_outPrefix}',
                            f'--topup={topup_results}',
                            eddy_openmp_params] & FG


                repol_bvecs = np.array(read_bvecs(outPrefix + '.eddy_rotated_bvecs'))
                wo_repol_bvecs = np.array(read_bvecs(wo_repol_outPrefix + '.eddy_rotated_bvecs'))

                merged_bvecs = repol_bvecs.copy()
                merged_bvecs[ind, :] = wo_repol_bvecs[ind, :]

                repol_data = load_nifti(outPrefix + '.nii.gz')
                wo_repol_data = load_nifti(wo_repol_outPrefix + '.nii.gz')
                merged_data = repol_data.get_fdata().copy()
                merged_data[..., ind] = wo_repol_data.get_fdata()[..., ind]

                save_nifti(outPrefix + '.nii.gz', merged_data, repol_data.affine, hdr=repol_data.header)

                # copy bval,bvec to have same prefix as that of eddy corrected volume
                write_bvecs(outPrefix + '.bvec', merged_bvecs)
                copyfile(modBvals, outPrefix + '.bval')
                
                # clean up
                rm['-r', wo_repol_outDir] & FG

            else:
                # copy bval,bvec to have same prefix as that of eddy corrected volume
                copyfile(outPrefix + '.eddy_rotated_bvecs', outPrefix + '.bvec')
                copyfile(modBvals, outPrefix + '.bval')





        # if self.force:
        #     logging.info('Deleting previous output directory')
        #     rm('-rf', self.outDir)


        temp= self.dwi_file.split(',')
        primaryVol= abspath(temp[0])
        if len(temp)<2:
            raise AttributeError('Two volumes are required for --imain')
        else:
            secondaryVol= abspath(temp[1])

        primaryMask=[]
        secondaryMask=[]
        if self.b0_brain_mask:
            temp = self.b0_brain_mask.split(',')
            primaryMask = abspath(temp[0])
            if len(temp) == 2:
                secondaryMask = abspath(temp[1])
        


        # obtain 4D/3D info and time axis info
        dimension = load_nifti(primaryVol).header['dim']
        dim1 = dimension[0]
        if dim1!=4:
            raise AttributeError('Primary volume must be 4D, however, secondary can be 3D/4D')
        numVol1 = dimension[4]

        dimension = load_nifti(secondaryVol).header['dim']
        dim2 = dimension[0]
        numVol2 = dimension[4]


        temp= self.bvals_file.split(',')
        if len(temp)>=1:
            primaryBval= abspath(temp[0])
        if len(temp)==2:
            secondaryBval= abspath(temp[1])
        elif len(temp)==1 and dim2==4:
            secondaryBval= primaryBval
        elif len(temp)==1 and dim2==3:
            secondaryBval=[]
        elif len(temp)==0:
            raise AttributeError('--bvals are required')

        temp= self.bvecs_file.split(',')
        if len(temp)>=1:
            primaryBvec= abspath(temp[0])
        if len(temp)==2:
            secondaryBvec= abspath(temp[1])
        elif len(temp) == 1 and dim2 == 4:
            secondaryBvec = primaryBvec
        elif len(temp)==1 and dim2==3:
            secondaryBvec=[]
        else:
            raise AttributeError('--bvecs are required')



        with local.cwd(self.outDir):

            # mask both volumes, fslmaths can do that irrespective of dimension
            logging.info('Masking the volumes')

            primaryMaskedVol = 'primary_masked.nii.gz'
            secondaryMaskedVol = 'secondary_masked.nii.gz'

            if primaryMask:
                # mask the volume
                fslmaths[primaryVol, '-mas', primaryMask, primaryMaskedVol] & FG
            else:
                primaryMaskedVol= primaryVol

            if secondaryMask:
                # mask the volume
                fslmaths[secondaryVol, '-mas', secondaryMask, secondaryMaskedVol] & FG
            else:
                secondaryMaskedVol= secondaryVol


            logging.info('Extracting B0 from masked volumes')
            B0_PA= 'B0_PA.nii.gz'
            B0_AP= 'B0_AP.nii.gz'

            obtainB0(primaryMaskedVol, primaryBval, B0_PA, self.num_b0)

            if dim2==4:
                obtainB0(secondaryMaskedVol, secondaryBval, B0_AP, self.num_b0)
            else:
                B0_AP= secondaryMaskedVol


            B0_PA_AP_merged = 'B0_PA_AP_merged.nii.gz'
            with open(self.acqparams_file._path) as f:
                acqp= f.read().strip().split('\n')
                if len(acqp)!=2:
                    raise ValueError('The acquisition parameter file must have exactly two lines')

            logging.info('Writing acqparams.txt for topup')

            # firstDim: first acqp line should be replicated this number of times
            firstB0dim= load_nifti(str(B0_PA)).header['dim'][4]
            # secondDim: second acqp line should be replicated this number of times
            secondB0dim= load_nifti(str(B0_AP)).header['dim'][4]
            acqp_topup= 'acqp_topup.txt'
            with open(acqp_topup,'w') as f:
                for i in range(firstB0dim):
                    f.write(acqp[0]+'\n')

                for i in range(secondB0dim):
                    f.write(acqp[1]+'\n')


            logging.info('Merging B0_PA and BO_AP')
            fslmerge('-t', B0_PA_AP_merged, B0_PA, B0_AP)


            topup_params, applytopup_params, eddy_openmp_params= obtain_fsl_eddy_params(self.eddy_config_file._path)

            # Example for topup
            # === on merged b0 images ===
            # https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide#Running_topup_on_the_b.3D0_volumes
            # topup --imain=P2A_A2P_b0 --datain=acqparams.txt --config=b02b0.cnf --out=my_output --iout=my_output
            # 
            # === on all b0 images ===
            # https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#Running_topup
            # topup --imain=all_b0s --datain=acqparams.txt --config=b02b0.cnf --out=my_output --iout=my_output


            logging.info('Running topup')
            topup_results= 'topup_out'
            topupOut= 'topup_out.nii.gz'
            
            # topup --iout yields as many volumes as there are input volumes
            # --iout specifies the name of a 4D image file that contains unwarped and movement corrected images.
            # each volume in the --imain will have a corresponding corrected volume in --iout.

            # --iout is used for creating modified mask only
            # when primary4D,secondary4D/3D are already masked, this will be useful
            topup[f'--imain={B0_PA_AP_merged}',
                  f'--datain={acqp_topup}',
                  f'--out={topup_results}',
                  f'--iout={topupOut}',
                  topup_params.split()] & FG
            
            # provide topupOutMean for quality checking
            topupOutMean= 'topup_out_mean.nii.gz'
            fslmaths[topupOut, '-Tmean', topupOutMean] & FG
            
            
            logging.info('Running applytopup')

            # applytopup always yields one output file regardless of one or two input files
            # if two input files are provided, the resulting undistorted file will be a combination of the two
            # containing only as many volumes as there are in one file
            
            # B0_PA_correct, B0_AP_correct are for quality checking only
            # primaryMaskCorrect, secondaryMaskCorrect will be associated masks
            B0_PA_correct= 'B0_PA_corrected.nii.gz'
            applytopup[f'--imain={B0_PA}',
                       f'--datain={self.acqparams_file}',
                       '--inindex=1',
                       f'--topup={topup_results}',
                       f'--out={B0_PA_correct}',
                       applytopup_params.split()] & FG

            B0_AP_correct= 'B0_AP_corrected.nii.gz'
            applytopup[f'--imain={B0_AP}',
                       f'--datain={self.acqparams_file}',
                       '--inindex=2',
                       f'--topup={topup_results}',
                       f'--out={B0_AP_correct}',
                       applytopup_params.split()] & FG

            B0_PA_AP_corrected_merged= 'B0_PA_AP_corrected_merged'
            fslmerge('-t', B0_PA_AP_corrected_merged, B0_PA_correct, B0_AP_correct)
            fslmaths[B0_PA_AP_corrected_merged, '-Tmean', 'B0_PA_AP_corrected_mean'] & FG


            
            topupMask= 'topup_mask.nii.gz'

            # calculate topup mask
            if primaryMask and secondaryMask:

                fslmaths[primaryMask, '-mul', '1', primaryMask, '-odt', 'float']
                fslmaths[secondaryMask, '-mul', '1', secondaryMask, '-odt', 'float']

                applytopup_params+=' --interp=trilinear'
                
                # this straightforward way could be used
                '''
                applytopup[f'--imain={primaryMask},{secondaryMask}',
                           f'--datain={self.acqparams_file}',
                           '--inindex=1,2',
                           f'--topup={topup_results}',
                           f'--out={topupMask}',
                           applytopup_params.split()] & FG
                '''
                # but let's do it step by step in order to have more control of the process
                


                # binarise the mean of corrected primary,secondary mask to obtain modified mask
                # use that mask for eddy_openmp
                primaryMaskCorrect = 'primary_mask_corrected.nii.gz'
                applytopup[f'--imain={primaryMask}',
                           f'--datain={self.acqparams_file}',
                           '--inindex=1',
                           f'--topup={topup_results}',
                           f'--out={primaryMaskCorrect}',
                           applytopup_params.split()] & FG

                secondaryMaskCorrect = 'secondary_mask_corrected.nii.gz'
                applytopup[f'--imain={secondaryMask}',
                           f'--datain={self.acqparams_file}',
                           '--inindex=2',
                           f'--topup={topup_results}',
                           f'--out={secondaryMaskCorrect}',
                           applytopup_params.split()] & FG

                fslmerge('-t', topupMask, primaryMaskCorrect, secondaryMaskCorrect)
                temp= load_nifti(topupMask)
                data= temp.get_fdata()
                data= abs(data[...,0])+ abs(data[...,1])
                data[data!=0]= 1

                # filter the mask to smooth edges
                # scale num of erosion followed by scale num of dilation
                # the greater the scale, the smoother the edges
                # scale=2 seems sufficient
                data= single_scale(data, int(self.scale))
                save_nifti(topupMask, data.astype('uint8'), temp.affine, temp.header)
                

            else:
                # this block assumes the primary4D,secondary4D/3D are already masked
                # then toupOutMean is also masked
                # binarise the topupOutMean to obtain modified mask
                # use that mask for eddy_openmp
                # fslmaths[topupOutMean, '-bin', topupMask, '-odt', 'char'] & FG

                # if --mask is not provided at all, this block creates a crude mask
                # apply bet on the mean of topup output to obtain modified mask
                # use that mask for eddy_openmp
                bet[topupOutMean, topupMask.split('_mask.nii.gz')[0], '-m', '-n'] & FG
                

                

            logging.info('Writing index.txt for topup')
            indexFile= 'index.txt'
            with open(indexFile, 'w') as f:
                for i in range(numVol1):
                    f.write('1\n')

            

            outPrefix = basename(primaryVol).split('.nii')[0]
            
            # remove _acq- 
            outPrefix= outPrefix.replace('_acq-PA','')
            outPrefix= outPrefix.replace('_acq-AP','')

            # find dir field
            if '_dir-' in primaryVol and '_dir-' in secondaryVol and self.whichVol == '1,2':
                dir= load_nifti(primaryVol).shape[3]+ load_nifti(secondaryVol).shape[3]
                outPrefix= local.path(re.sub('_dir-(.+?)_', f'_dir-{dir}_', outPrefix))


            outPrefix = outPrefix + '_EdEp'
            with open('.outPrefix.txt', 'w') as f:
                f.write(outPrefix)
            


            temp = self.whichVol.split(',')
            if len(temp)==1 and temp[0]=='1':
                # correct only primary4D volume

                _eddy_openmp(primaryMaskedVol, primaryBval, primaryBvec, eddy_openmp_params)


            elif len(temp)==2 and temp[1]=='2':
                # sylvain would like to correct both primary and secondary volumes


                with open(indexFile, 'a') as f:
                    for i in range(numVol2):
                        f.write('2\n')


                # join both bvalFiles
                bvals1= read_bvals(primaryBval)
                if dim2==4 and not secondaryBval:
                    bvals2= bvals1.copy()
                elif dim2==4 and secondaryBval:
                    bvals2= read_bvals(secondaryBval)
                elif dim2==3:
                    bvals2=[0]

                combinedBvals = 'combinedBvals.txt'
                write_bvals(combinedBvals, bvals1+bvals2)

                # join both bvecFiles
                bvecs1= read_bvecs(primaryBvec)
                if dim2==4 and not secondaryBvec:
                    bvecs2= bvecs1.copy()
                elif dim2==4 and secondaryBvec:
                    bvecs2= read_bvecs(secondaryBvec)
                elif dim2==3:
                    bvecs2=[[0,0,0]]

                # join both bvecFiles
                combinedBvecs = 'combinedBvecs.txt'
                write_bvecs(combinedBvecs, bvecs1+bvecs2)

                combinedData= 'combinedData.nii.gz'
                fslmerge('-t', combinedData, primaryMaskedVol, secondaryMaskedVol)


                _eddy_openmp(combinedData, combinedBvals, combinedBvecs, eddy_openmp_params)
                

            else:
                raise ValueError('Invalid --whichVol')

            # rename topupMask to have same prefix as that of eddy corrected volume
            move(topupMask, outPrefix + '_mask.nii.gz')