Example #1
0
    def test_nrrd2nifti(self):

        PREFIX = pjoin(outdir, 'nrrd2nifti_prefix')

        # run nrrd2nifti conversion
        conversion.nifti_write(REFERENCE_NRRD, PREFIX)

        # load converted output
        converted_nifti = load(PREFIX + '.nii.gz')
        converted_nifti_data = converted_nifti.get_data()
        converted_nifti_affine = converted_nifti.affine
        converted_nifti_bvals = conversion.read_bvals(PREFIX + '.bval')
        converted_nifti_bvecs = conversion.read_bvecs(PREFIX + '.bvec')

        # load converted reference
        converted_nifti = load(CONVERTED_NIFTI)
        reference_nifti_data = converted_nifti.get_data()
        reference_nifti_affine = converted_nifti.affine
        reference_nifti_bvals = conversion.read_bvals(CONVERTED_BVAL)
        reference_nifti_bvecs = conversion.read_bvecs(CONVERTED_BVEC)

        # test equality
        testing.assert_array_equal(reference_nifti_data, converted_nifti_data)
        testing.assert_array_equal(reference_nifti_affine,
                                   converted_nifti_affine)
        testing.assert_array_equal(reference_nifti_bvals,
                                   converted_nifti_bvals)
        testing.assert_array_equal(reference_nifti_bvecs,
                                   converted_nifti_bvecs)
Example #2
0
def joinBshells(imgPath, ref_bvals_file=None, ref_bvals=None, sep_prefix=None):

    if ref_bvals_file:
        print('Reading reference b-shell file ...')
        ref_bvals = read_bvals(ref_bvals_file)

    print('Joining b-shells for', imgPath)

    imgPath = local.path(imgPath)
    img = load(imgPath._path)
    dim = img.header['dim'][1:5]

    inPrefix = abspath(imgPath).split('.nii')[0]
    directory = dirname(inPrefix)
    prefix = basename(inPrefix)

    bvalFile = inPrefix + '.bval'
    bvecFile = inPrefix + '.bvec'

    if sep_prefix:
        harmPrefix = pjoin(directory, sep_prefix + prefix)
    else:
        harmPrefix = inPrefix

    if not isfile(harmPrefix + '.bval'):
        copyfile(bvalFile, harmPrefix + '.bval')
    if not isfile(harmPrefix + '.bvec'):
        copyfile(bvecFile, harmPrefix + '.bvec')

    bvals = np.array(read_bvals(inPrefix + '.bval'))

    joinedDwi = np.zeros((dim[0], dim[1], dim[2], dim[3]), dtype='float32')

    for bval in ref_bvals:

        # ind= np.where(bval==bvals)[0]
        ind = np.where(abs(bval - bvals) <= BSHELL_MIN_DIST)[0]

        if bval == 0.:
            b0Img = load(inPrefix + '_b0.nii.gz')
            b0 = b0Img.get_data()
            for i in ind:
                joinedDwi[:, :, :, i] = b0

        else:
            b0_bshell = load(harmPrefix + f'_b{int(bval)}.nii.gz').get_data()

            joinedDwi[:, :, :, ind] = b0_bshell[:, :, :, 1:]

    if not isfile(harmPrefix + '.nii.gz'):
        save_nifti(harmPrefix + '.nii.gz', joinedDwi, b0Img.affine,
                   b0Img.header)
    else:
        print(harmPrefix + '.nii.gz', 'already exists, not overwritten.')
Example #3
0
def bval_bvec_difference(imgPath_given_mat, imgPath_given_py, caselist):
    bval_diff_list = []
    bval_total_diff = []
    bval_mean_diff = []

    bvec_diff_list = []
    bvec_total_diff = []
    bvec_mean_diff = []

    for case in caselist:
        imgPath_mat = imgPath_given_mat.replace('XYZ', case)
        imgPath_py = imgPath_given_py.replace('XYZ', case)

        # load python
        bvals_py = np.array(read_bvals(imgPath_py.split('.nii')[0] + '.bval'))
        bvecs_py = np.array(read_bvecs(imgPath_py.split('.nii')[0] + '.bvec'))

        bvals_py, bvecs_py = stack_one_b0(bvals_py, bvecs=bvecs_py)
        bvals_py = bvals_py.astype('float64')
        bvecs_py = bvecs_py.astype('float64')

        # load matlab
        bvals_mat = np.array(
            read_bvals(imgPath_mat.split('.nii')[0] + '.bval'))
        bvecs_mat = np.array(
            read_bvecs(imgPath_mat.split('.nii')[0] + '.bvec'))
        bvals_mat = bvals_mat.astype('float64')
        bvecs_mat = bvecs_mat.astype('float64')

        # obtain case wise difference for bvals
        diff = 2 * abs(bvals_py - bvals_mat) / (bvals_py + bvals_mat) * 100
        bval_diff_list.append(diff.flatten())
        bval_mean_diff.append(diff.mean())

        # obtain case wise difference
        diff = 2 * abs(bvecs_py - bvecs_mat) / (bvals_py + bvals_mat) * 100
        bvec_diff_list.append(diff.flatten())
        bvec_mean_diff.append(diff.mean())

    print(
        f'Mean of mean relative percentage difference over all the bvals: {np.mean(bval_mean_diff)}'
    )
    hist_calc(bval_diff_list, bins=BINS)
    print('\n\n')

    print(
        f'Mean of mean relative percentage difference over all the bvecs: {np.mean(bvec_mean_diff)}'
    )
    hist_calc(bvec_diff_list, bins=BINS)
    print('\n\n')
Example #4
0
def separateBshells(imgPath, ref_bvals_file=None, ref_bvals=None):

    if ref_bvals_file:
        print('Reading reference b-shell file ...')
        ref_bvals = read_bvals(ref_bvals_file)

    print('Separating b-shells for', imgPath)

    imgPath = local.path(imgPath)

    img = load(imgPath._path)
    dwi = img.get_data()
    inPrefix = abspath(imgPath).split('.nii')[0]
    bvals = np.array(read_bvals(inPrefix + '.bval'))
    bvecs = np.array(read_bvecs(inPrefix + '.bvec'))

    for bval in ref_bvals:

        # ind= np.where(bval==bvals)[0]
        ind = np.where(abs(bval - bvals) <= BSHELL_MIN_DIST)[0]
        N_b = len(ind)

        bPrefix = inPrefix + f'_b{int(bval)}'

        if bval == 0.:
            b0 = find_b0(dwi, ind)

        if isfile(bPrefix + '.nii.gz'):
            continue

        if bval == 0.:
            save_nifti(bPrefix + '.nii.gz', b0, img.affine, img.header)

        else:
            b0_bshell = np.zeros(
                (dwi.shape[0], dwi.shape[1], dwi.shape[2], N_b + 1),
                dtype='float32')
            b0_bshell[:, :, :, 0] = b0
            b0_bshell[:, :, :, 1:] = dwi[:, :, :, ind]

            b0_bvals = [0.] + [bval] * N_b

            b0_bvecs = np.zeros((N_b + 1, 3), dtype='float32')
            b0_bvecs[1:, ] = bvecs[ind, :]

            save_nifti(bPrefix + '.nii.gz', b0_bshell, img.affine, img.header)
            write_bvals(bPrefix + '.bval', b0_bvals)
            write_bvecs(bPrefix + '.bvec', b0_bvecs)
def separateBshells(imgPath, ref_bvals_file=None, ref_bvals=None):

    if ref_bvals_file:
        print('Reading reference b-shell file ...')
        ref_bvals = read_bvals(ref_bvals_file)

    print('Separating b-shells for', imgPath)

    imgPath = local.path(imgPath)

    img = load(imgPath._path)
    dwi = img.get_data()
    inPrefix = abspath(imgPath).split('.nii')[0]
    bvals = np.array(read_bvals(inPrefix + '.bval'))
    bvecs = np.array(read_bvecs(inPrefix + '.bvec'))

    for bval in ref_bvals:

        # ind= np.where(bval==bvals)[0]
        ind = np.where(abs(bval - bvals) <= BSHELL_MIN_DIST)[0]
        N_b = len(ind)

        bPrefix = inPrefix + f'_b{int(bval)}'

        if bval == 0.:
            b0 = find_b0(dwi, ind)

        else:
            b0_bshell = np.zeros(
                (dwi.shape[0], dwi.shape[1], dwi.shape[2], N_b + 1),
                dtype='float32')
            b0_bshell[:, :, :, 0] = b0
            b0_bshell[:, :, :, 1:] = dwi[:, :, :, ind]

            b0_bvals = [0.] + [bval] * N_b

            b0_bvecs = np.zeros((N_b + 1, 3), dtype='float32')
            b0_bvecs[1:, ] = bvecs[ind, :]

            bshell_written = load(bPrefix + '.nii.gz').get_fdata()
            bvals_written = read_bvals(bPrefix + '.bval')
            bvecs_written = read_bvecs(bPrefix + '.bvec')

            print('bshell difference', abs(b0_bshell - bshell_written).sum())
            print('bvals difference',
                  (np.array(b0_bvals) - np.array(bvals_written)).sum())
            print('bvecs difference',
                  (np.array(b0_bvecs) - np.array(bvecs_written)).sum())
Example #6
0
def determineNshm(bvalFile):

    print(
        f'Determining maximum possible order of spherical harmonics for {bvalFile}\n'
    )

    bvals = np.array(read_bvals(bvalFile))
    N_b = len(np.where(bvals > B0_THRESH)[0])

    if N_b < 6:
        raise ValueError(
            f'At least 6 gradients are necessary for each b-shell, b-shell has only {N_b}'
        )
    elif N_b >= 6 and N_b < 15:
        N_shm = 2
    elif N_b >= 15 and N_b < 28:
        N_shm = 4
    elif N_b >= 28 and N_b < 45:
        N_shm = 6
    else:
        N_shm = 8

    print(f'Maximum possible order is {N_shm} for {N_b} non-zero gradients\n')

    return (N_shm, N_b)
def consistencyCheck(ref_csv,
                     outputBshellFile=None,
                     outPutResolutionFile=None):

    try:
        ref_imgs, _ = read_imgs_masks(ref_csv)
    except:
        ref_imgs = read_imgs(ref_csv)

    if isfile(outputBshellFile) and isfile(outPutResolutionFile):
        ref_bvals = read_bvals(outputBshellFile)
        ref_res = np.load(outPutResolutionFile)
    else:
        ref_bshell_img = ref_imgs[0]
        print(f'Using {ref_bshell_img} to determine b-shells')

        inPrefix = abspath(ref_bshell_img).split('.nii')[0]
        ref_bvals = findBShells(inPrefix + '.bval', outputBshellFile)

        ref_res = load(ref_bshell_img).header['pixdim'][1:4]
        np.save(outPutResolutionFile, ref_res)

    print('b-shells are', ref_bvals)

    print('\nSite', ref_csv, '\n')

    print('Checking consistency of b-shells among subjects')
    check_bshells(ref_imgs, ref_bvals)

    print('spatial resolution is', ref_res)
    print('Checking consistency of spatial resolution among subjects')
    check_resolution(ref_imgs, ref_res)
def findBShells(bvalFile, outputBshellFile= None):

    given_bvals= read_bvals(abspath(bvalFile))

    # get unique bvalues in ascending order
    unique_bvals= np.unique(given_bvals)

    # identify b0s
    quantized_bvals= unique_bvals.copy()
    quantized_bvals[unique_bvals<=B0_THRESH]= 0.

    # round to multiple of B_QUANT (50 or 100)
    quantized_bvals= np.unique(np.round(quantized_bvals/B_QUANT)*B_QUANT)

    print('b-shell bvalues', quantized_bvals)

    for bval in quantized_bvals:
        print('Indices corresponding to b-shell', bval)
        print(np.where(abs(bval-given_bvals)<=BSHELL_MIN_DIST)[0],'\n')


    if outputBshellFile:
        print('Saving the b-shell bvalues in', outputBshellFile)
        write_bvals(outputBshellFile, quantized_bvals)

    return quantized_bvals
Example #9
0
    def main(self):

        prefix = self.dwi.name.split('.')[0]
        directory = self.dwi.parent

        self.b0_threshold = float(self.b0_threshold)

        if self.out is None:
            self.out = os.path.join(directory, prefix + '_bse.nii.gz')

        if self.dwi.endswith('.nii') or self.dwi.endswith('.nii.gz'):

            if not self.bval_file:
                self.bval_file = os.path.join(directory, prefix + '.bval')

            bvals = read_bvals(self.bval_file)
            idx = np.where([bval < self.b0_threshold for bval in bvals])[0]

            if len(idx) > 1:

                # default is the first b0
                if not (self.minimum or self.average or self.all):
                    fslroi[self.dwi, self.out, idx, 1] & FG

                elif self.minimum:
                    fslroi[self.dwi, self.out, idx, np.argsort(bvals)[0]] & FG

                elif self.average:
                    # Load the given dwi to get image data
                    dwi = load_nifti(self.dwi._path)
                    hdr = dwi.header
                    mri = dwi.get_data()

                    avg_bse = np.mean(mri[:, :, :, idx], axis=3)

                    # Now write back the average bse
                    save_nifti(self.out, avg_bse, dwi.affine, hdr)

                elif self.all:
                    fslroi[self.dwi, self.out, idx, len(idx)] & FG

            else:
                raise Exception('No b0 image found. Check the bval file.')

        else:
            raise Exception("Invalid dwi format, must be a nifti image")

        if self.dwimask:
            ImageMath(3, self.out, 'm', self.out, self.dwimask)
Example #10
0
def separateAllBshells(ref_csv, ref_bvals_file, ncpu=4, outPrefix=None):

    ref_bvals = read_bvals(ref_bvals_file)

    try:
        imgs, masks = read_imgs_masks(ref_csv)
    except:
        imgs = read_imgs(ref_csv)
        masks = None

    pool = Pool(int(ncpu))
    for imgPath in imgs:
        pool.apply_async(separateBshells,
                         kwds={
                             'imgPath': imgPath,
                             'ref_bvals': ref_bvals
                         },
                         error_callback=RAISE)

    pool.close()
    pool.join()

    if outPrefix:
        outPrefix = abspath(outPrefix)
    else:
        return

    for bval in ref_bvals:

        f = open(f'{outPrefix}_b{int(bval)}.csv', 'w')

        if masks:
            for imgPath, maskPath in zip(imgs, masks):
                inPrefix = abspath(imgPath).split('.nii')[0]
                bPrefix = inPrefix + f'_b{int(bval)}'
                f.write(f'{bPrefix}.nii.gz,{maskPath}\n')

        else:
            for imgPath in imgs:
                inPrefix = abspath(imgPath).split('.nii')[0]
                bPrefix = inPrefix + f'_b{int(bval)}'
                f.write(f'{bPrefix}.nii.gz\n')

        f.close()
def joinAllBshells(tar_csv, ref_bvals_file, separatedPrefix=None, ncpu=4):

    ref_bvals = read_bvals(ref_bvals_file)
    if tar_csv:

        try:
            imgs, _ = read_imgs_masks(tar_csv)
        except:
            imgs = read_imgs(tar_csv)

        pool = Pool(int(ncpu))
        for imgPath in imgs:
            pool.apply_async(joinBshells,
                             kwds=({
                                 'imgPath': imgPath,
                                 'ref_bvals': ref_bvals,
                                 'sep_prefix': separatedPrefix
                             }),
                             error_callback=RAISE)

        pool.close()
        pool.join()
Example #12
0
def bet_mask(imgPath, maskPath, dim, bvalFile=None, thr=BET_THRESHOLD):

    with local.tempdir() as tmpdir:
        bsetmp = tmpdir / 'bse.nii.gz'

        if dim == 4:
            bvals = read_bvals(bvalFile)
            idx = np.where([bval < B0_THRESHOLD for bval in bvals])[0]

            if len(idx) >= 1:
                fslroi[imgPath, bsetmp, idx, 1] & FG

                bet[bsetmp, maskPath, '-m', '-n', '-f', thr] & FG

            else:
                raise Exception('No b0 image found. Check the bval file.')

        elif dim == 3:
            bet[imgPath, maskPath, '-m', '-n', '-f', thr] & FG

        else:
            raise ValueError('Input dimension should be 3 or 4')
Example #13
0
def dwi_difference(imgPath_given_mat, imgPath_given_py, caselist):
    diff_list = []
    total_diff = []
    mean_diff = []

    for case in caselist:
        imgPath_mat = imgPath_given_mat.replace('XYZ', case)
        imgPath_py = imgPath_given_py.replace('XYZ', case)

        # load python
        img_py_obj = nib.load(imgPath_py)
        img_py = img_py_obj.get_fdata()
        # keep only the first b0
        bvals = np.array(read_bvals(imgPath_py.split('.nii')[0] + '.bval'))
        img_py = stack_one_b0(bvals, dwi=img_py)
        img_py = img_py.astype('float64')

        pyX, pyY, pyZ, _ = img_py.shape

        # load matlab
        img_mat = nib.load(imgPath_mat).get_fdata()[:pyX, :pyY, :pyZ, :]
        img_mat = img_mat.astype('float64')

        # obtain case wise difference
        diff = 2 * abs(img_py - img_mat) / (img_py +
                                            img_mat).clip(min=1.) * 100
        diff_list.append(diff.flatten())
        mean_diff.append(diff.mean())

        # save the difference mask
        nib.Nifti1Image(diff, img_py_obj.affine,
                        img_py_obj.header).to_filename(
                            imgPath_mat.split('.nii')[0] + '_diff_mask.nii.gz')

    print(
        f'Mean of mean relative percentage difference over all the voxels: {np.mean(mean_diff)}'
    )
    hist_calc(diff_list, bins=BINS)
Example #14
0
def pre_process(input_file, target_list, b0_threshold=50.):

    from conversion import nifti_write, read_bvals
    from subprocess import Popen

    if path.isfile(input_file):

        # convert NRRD/NHDR to NIFIT as the first step
        # extract bse.py from just NIFTI later
        if input_file.endswith(SUFFIX_NRRD) | input_file.endswith(SUFFIX_NHDR):
            inPrefix = input_file.split('.')[0]
            nifti_write(input_file)
            input_file = inPrefix + '.nii.gz'

        inPrefix = input_file.split('.nii')[0]
        b0_nii = path.join(inPrefix + '_bse.nii.gz')

        dwi = nib.load(input_file)

        if len(dwi.shape) > 3:
            print("Extracting b0 volume...")
            bvals = np.array(read_bvals(input_file.split('.nii')[0] + '.bval'))
            where_b0 = np.where(bvals <= b0_threshold)[0]
            b0 = dwi.get_data()[..., where_b0].mean(-1)
        else:
            print("Loading b0 volume...")
            b0 = dwi.get_fdata()

        np.nan_to_num(b0).clip(min=0., out=b0)
        nib.Nifti1Image(b0, affine=dwi.affine,
                        header=dwi.header).to_filename(b0_nii)

        target_list.append((b0_nii))

    else:
        print("File not found ", input_file)
        sys.exit(1)
Example #15
0
        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')
    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)
Example #17
0
        def _eddy_openmp(modData, modBvals, modBvecs, eddy_openmp_params):

            print('eddy_openmp/cuda parameters')
            print(eddy_openmp_params)
            print('')

            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

            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 '
                    'to obtain eddy correction w/o outlier replacement for b<=500 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(outPrefix + '.nii.gz')
                wo_repol_data = load(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')

            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')
Example #18
0
    def main(self):

        from plumbum.cmd import eddy_openmp

        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'
                )

        prefix = self.dwi_file.name.split('.')[0]
        self.outDir.mkdir()
        outPrefix = pjoin(self.outDir._path, prefix + '_Ed')

        if not self.b0_brain_mask:
            logging.info('Mask not provided, creating mask ...')

            self.b0_brain_mask = outPrefix + '_mask.nii.gz'

            bet_mask(self.dwi_file,
                     self.b0_brain_mask,
                     4,
                     bvalFile=self.bvals_file,
                     BET_THRESHOLD=self.betThreshold)

        _, _, eddy_openmp_params = obtain_fsl_eddy_params(
            self.eddy_config_file._path)

        print('eddy_openmp/cuda parameters')
        print(eddy_openmp_params)
        print('')

        eddy_openmp[f'--imain={self.dwi_file}', f'--mask={self.b0_brain_mask}',
                    f'--acqp={self.acqparams_file}',
                    f'--index={self.index_file}', f'--bvecs={self.bvecs_file}',
                    f'--bvals={self.bvals_file}', f'--out={outPrefix}',
                    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(self.bvals_file))
        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 '
                'to obtain eddy correction w/o outlier replacement for b<=500 shells\n'
            )

            eddy_openmp_params = eddy_openmp_params.split()
            eddy_openmp_params.remove('--repol')
            print(eddy_openmp_params)
            print('')
            wo_repol_outDir = self.outDir.join('wo_repol')
            wo_repol_outDir.mkdir()
            wo_repol_outPrefix = pjoin(wo_repol_outDir, prefix + '_Ed')

            eddy_openmp[
                f'--imain={self.dwi_file}', f'--mask={self.b0_brain_mask}',
                f'--acqp={self.acqparams_file}', f'--index={self.index_file}',
                f'--bvecs={self.bvecs_file}', f'--bvals={self.bvals_file}',
                f'--out={wo_repol_outPrefix}', 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(self.bvals_file, 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(self.bvals_file, outPrefix + '.bval')
Example #19
0
    def main(self):

        if self.N_proc == '-1':
            self.N_proc = N_CPU

        # check directory existence
        check_dir(self.templatePath, self.force)

        ## check consistency of b-shells and spatial resolution
        ref_bvals_file = pjoin(self.templatePath, 'ref_bshell_bvalues.txt')
        ref_res_file = pjoin(self.templatePath, 'ref_res_file.npy')
        if self.ref_csv:
            if isfile(ref_bvals_file) and isfile(ref_res_file):
                remove(ref_bvals_file)
                remove(ref_res_file)

            consistencyCheck(self.ref_csv, ref_bvals_file, ref_res_file)

        if self.target_csv:
            consistencyCheck(self.target_csv, ref_bvals_file, ref_res_file)

        ## separate b-shells
        if self.ref_csv:
            refListOutPrefix = separateShellsWrapper(self.ref_csv,
                                                     ref_bvals_file,
                                                     self.N_proc)
        if self.target_csv:
            tarListOutPrefix = separateShellsWrapper(self.target_csv,
                                                     ref_bvals_file,
                                                     self.N_proc)

        ## define variables for template creation and data harmonization

        # variables common to all ref_bvals
        pipeline_vars = [
            '--tar_name',
            self.target,
            '--nshm',
            self.N_shm,
            '--nproc',
            self.N_proc,
            '--template',
            self.templatePath,
        ]

        if self.reference:
            pipeline_vars.append(f'--ref_name {self.reference}')
        if self.N_zero:
            pipeline_vars.append(f'--nzero {self.N_zero}')
        if self.bvalMap:
            pipeline_vars.append(f'--bvalMap {self.bvalMap}')
        if self.resample:
            pipeline_vars.append(f'--resample {self.resample}')
        if self.denoise:
            pipeline_vars.append('--denoise')
        if self.travelHeads:
            pipeline_vars.append('--travelHeads')
        if self.force:
            pipeline_vars.append('--force')
        if self.debug:
            pipeline_vars.append('--debug')
        if self.verbose:
            pipeline_vars.append('--verbose')

        # the b-shell bvalues are sorted in descending order because we want to perform registration with highest bval
        ref_bvals = read_bvals(ref_bvals_file)[::-1]
        for bval in ref_bvals[:-1]:  # pass the last bval which is 0.

            if self.create and not self.process:
                print('## template creation ##')

                check_call((' ').join([
                    pjoin(SCRIPTDIR, 'harmonization.py'), '--tar_list',
                    tarListOutPrefix + f'_b{int(bval)}.csv', '--bshell_b',
                    str(int(bval)), '--ref_list', refListOutPrefix +
                    f'_b{int(bval)}.csv', '--create'
                ] + pipeline_vars),
                           shell=True)

            elif not self.create and self.process:
                print('## data harmonization ##')

                check_call((' ').join([
                    pjoin(SCRIPTDIR, 'harmonization.py'), '--tar_list',
                    tarListOutPrefix + f'_b{int(bval)}.csv',
                    f'--ref_list {refListOutPrefix}_b{int(bval)}.csv' if self.
                    ref_csv else '', '--bshell_b',
                    str(int(bval)), '--process'
                ] + pipeline_vars),
                           shell=True)

            elif self.create and self.process:
                check_call((' ').join([
                    pjoin(SCRIPTDIR, 'harmonization.py'), '--tar_list',
                    tarListOutPrefix + f'_b{int(bval)}.csv', '--bshell_b',
                    str(int(bval)), '--ref_list', refListOutPrefix +
                    f'_b{int(bval)}.csv', '--create', '--process'
                ] + pipeline_vars),
                           shell=True)

            if '--force' in pipeline_vars:
                pipeline_vars.remove('--force')

        ## join harmonized data
        if self.process:
            joinAllBshells(self.target_csv, ref_bvals_file, 'harmonized_',
                           self.N_proc)

            if self.debug and self.ref_csv:
                joinAllBshells(self.ref_csv, ref_bvals_file, 'reconstructed_',
                               self.N_proc)
Example #20
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')