Example #1
0
def process(args):

    cases= read_cases(args.caselist)
    cases.sort()

    # organize images into different directories ===========================================================

    # outDir
    #    |
    # ------------------------------------------------------------------------------------------------------
    #    |           |             |                |        |       |                   |           |
    #    |           |             |                |        |       |                   |           |
    # transform   template        FA                MD       AD      RD                 log        stats
    #                              |       (same inner file structure as that of FA)
    #                              |
    #                 ----------------------------------------
    #                  |         |         |       |        |
    #                 preproc  origdata  warped  skeleton  roi
    #
    # copy all FA into FA directory
    # put all preprocessed data into preproc directory
    # keep all warp/affine in transform directory
    # output all warped images in warped directory
    # output all skeletons in skel directory
    # output ROI based analysis files in roi directory
    # save all ROI statistics, mean, and combined images


    # define directories
    modDir = pjoin(args.outDir, f'{args.modality}')
    # args.xfrmDir = pjoin(args.outDir, 'transform')
    # args.statsDir = pjoin(args.outDir, 'stats')
    templateDir = pjoin(args.outDir, 'template/')  # trailing slash is important for antsMultivariate*.sh
    preprocDir= pjoin(modDir, 'preproc')
    warpDir= pjoin(modDir, 'warped')
    skelDir= pjoin(modDir, 'skeleton')
    roiDir= pjoin(modDir, 'roi')

    # force creation of inner directories
    makeDirectory(warpDir, True)
    makeDirectory(skelDir, True)
    makeDirectory(roiDir, True)


    # modality can be one of [FA,MD,AD,RD]
    # we could use just listdir(), but the following would be stricter and safer
    # since cases are sorted and we named images as modDir/{c}.nii.gz
    # the following sort puts modImgs in the same order as that of cases
    modImgs = glob(pjoin(modDir, '*.nii.gz'))
    modImgs.sort()


    if not args.noFillHole:
        print('\nFilling holes inside the brain region in diffusion measure images')
        # fill holes in all modality images
        # caveat: origdata no longer remain origdata, become hole filled origdata
        pool= Pool(args.ncpu)
        pool.map_async(fillHoles, modImgs, error_callback= RAISE)
        pool.close()
        pool.join()


    # preprocessing ========================================================================================
    if args.modality=='FA':
        print('Preprocessing FA images: eroding them and zeroing the end slices ...')
        modDir= pjoin(args.outDir, args.modality)
        CURRDIR= getcwd()
        chdir(modDir)
        check_call('tbss_1_preproc *.nii.gz', shell= True) # creates 'FA' and 'origdata' folders
        chdir(CURRDIR)
        print('Index file location has changed, see ', pjoin(preprocDir, 'slicesdir', 'index.html'))

        # rename args.modality/FA to args.modality/preproc
        move(pjoin(modDir, 'FA'), preprocDir)
    else:
        print(f'Preprocessing {args.modality} images using FA mask (eroding them and zeroing the end slices) ...')
        modDir = pjoin(args.outDir, args.modality)

        # force creation of inner directories
        makeDirectory(pjoin(modDir, 'origdata'), True)
        makeDirectory(pjoin(modDir, 'preproc'), True)

        pool= Pool(args.ncpu)
        for c, imgPath in zip(cases, modImgs):
            FAmask= pjoin(args.outDir, 'FA', 'preproc', f'{c}_FA_mask.nii.gz')
            preprocMod= pjoin(preprocDir, f'{c}_{args.modality}.nii.gz')

            pool.apply_async(_fslmask, (imgPath, FAmask, preprocMod), error_callback= RAISE)


        pool.close()
        pool.join()

        check_call((' ').join(['mv', pjoin(modDir, '*.nii.gz'), pjoin(modDir, 'origdata')]), shell= True)

    modImgs = glob(pjoin(preprocDir, f'*{args.modality}.nii.gz'))
    modImgs.sort()

    # create template ======================================================================================
    if not args.template and args.modality=='FA':
        print('Creating study specific template ...')
        # we could pass modImgs directly to antsMult(), instead saving them to a .txt file for logging
        # modImgs = glob(pjoin(preprocDir, f'*{args.modality}*.nii.gz'))

        makeDirectory(templateDir, args.force)

        antsMultCaselist = pjoin(args.logDir, 'antsMultCaselist.txt')
        with open(antsMultCaselist, 'w') as f:
            for imgPath in modImgs:
                f.write(imgPath+'\n')

        # ATTN: antsMultivariateTemplateConstruction2.sh requires '/' at the end of templateDir
        antsMult(antsMultCaselist, templateDir, args.logDir, args.ncpu, args.verbose)
        # TODO: rename the template
        args.template= pjoin(templateDir, 'template0.nii.gz')
        check_call(f'ln -s {args.template} {args.statsDir}', shell= True)

        # warp and affine to template0.nii.gz have been created for each case during template construction
        # so template directory should be the transform directory
        args.xfrmDir= templateDir

    # register each image to the template ==================================================================
    elif args.template:
        # find warp and affine of FA image to args.template for each case
        if args.modality=='FA':
            print(f'Registering FA images to {args.template} space ..')
            makeDirectory(args.xfrmDir, True)
            pool= Pool(args.ncpu)
            for c, imgPath in zip(cases, modImgs):
                pool.apply_async(antsReg, (args.template, imgPath, pjoin(args.xfrmDir, f'{c}_FA'), args.logDir, args.verbose),
                                error_callback= RAISE)

            pool.close()
            pool.join()


    # register template to a standard space ================================================================
    # useful when you would like to do ROI based analysis using an atlas
    # project the created/specified template to the space of atlas
    if args.space:
        outPrefix = pjoin(args.xfrmDir, 'tmp2space')
        warp2space = outPrefix + '1Warp.nii.gz'
        trans2space = outPrefix + '0GenericAffine.mat'
        if not isfile(warp2space):
            print(f'Registering {args.template} to the space of {args.space} ...')
            antsReg(args.space, args.template, outPrefix, args.logDir, args.verbose)

        # TODO: rename the template
        args.template = outPrefix + 'Warped.nii.gz'
        if basename(args.template) not in listdir(args.statsDir):
            check_call(f'ln -s {args.template} {args.statsDir}', shell= True)
        
    pool= Pool(args.ncpu)
    for c, imgPath in zip(cases, modImgs):
        # generalize warp and affine
        warp2tmp= glob(pjoin(args.xfrmDir, f'{c}_FA*1Warp.nii.gz'))[0]
        trans2tmp= glob(pjoin(args.xfrmDir, f'{c}_FA*0GenericAffine.mat'))[0]
        output= pjoin(warpDir, f'{c}_{args.modality}_to_target.nii.gz')

        if not args.space:
            # print(f'Warping {imgPath} to template space ...')
            pool.apply_async(_antsApplyTransforms, (imgPath, output, args.template, warp2tmp, trans2tmp),
                            error_callback= RAISE)


        else:
            # print(f'Warping {imgPath} to template-->standard space ...')
            pool.apply_async(_antsApplyTransforms, (imgPath, output, args.space, warp2tmp, trans2tmp, warp2space, trans2space),
                            error_callback= RAISE)


    pool.close()
    pool.join()
    

    # create skeleton for each subject
    modImgsInTarget= glob(pjoin(warpDir, f'*_{args.modality}_to_target.nii.gz'))
    modImgsInTarget.sort()

    miFile= None
    if args.modality=='FA':
        print(f'Logging MI between warped images {warpDir}/*.nii.gz and target {args.template} ...')
        miFile= measureSimilarity(modImgsInTarget, cases, args.template, args.logDir, args.ncpu)


    # obtain modified args from skeletonize() which will be used for other modalities than FA
    args= skeletonize(modImgsInTarget, cases, args, skelDir, miFile)

    skelImgsInSub= glob(pjoin(skelDir, f'*_{args.modality}_to_target_skel.nii.gz'))
    skelImgsInSub.sort()

    # roi based analysis
    if args.labelMap:
        roi_analysis(skelImgsInSub, cases, args, roiDir, args.ncpu)

    return args
Example #2
0
def skeletonize(imgs, cases, args, skelDir, miFile):

    target = load(args.template)
    targetData = target.get_data()
    X, Y, Z = targetData.shape[0], targetData.shape[1], targetData.shape[2]

    # provide the user with allFA sequence so he knows which volume he is looking at while scrolling through allFA
    seqFile = pjoin(args.statsDir, f'all_{args.modality}_sequence.txt')
    with open(seqFile, 'w') as f:
        f.write('index,caseid\n')
        for i, c in enumerate(cases):
            f.write(f'{i},{c}\n')

    print(f'Calculating mean {args.modality} over all the cases ...')
    allFAdata, cumsumFA = calc_mean(imgs, (X, Y, Z), args.qc)

    if args.qc:
        allFA = pjoin(args.statsDir, f'all_{args.modality}.nii.gz')
        save_nifti(allFA, np.moveaxis(allFAdata, 0, -1), target.affine,
                   target.header)

        print(
            f'''\n\nQC the warped {args.modality} images: {allFA}, view {seqFile} for index of volumes in all_FA.nii.gz. 
You may use fsleyes/fslview to load {allFA}.

MI metric b/w the warped images and target are stored in {miFile}

It might be helpful to re-run registration for warped images that are bad.

Moving images are   :   {args.outDir}/preproc/
Target is           :   {args.template}
Transform files are :   {args.xfrmDir}/
Warped images are   :   {args.outDir}/warped/

Save any re-registered images in {args.outDir}/warped/ with the same name as before

For re-registration of any subject, output the transform files to a temporary directory:
        
        mkdir /tmp/badRegistration/
        
        antsRegistrationSyNQuick.sh -d 3 \\
        -f TEMPLATE \\
        -m FA/preproc/caseid_FA.nii.gz \\
        -o /tmp/badRegistration/caseid_FA
        
        antsApplyTransforms -d 3 \\
        -i FA/preproc/caseid_FA.nii.gz \\
        -o FA/warped/caseid_[FA/MD/AD/RD]_to_target.nii.gz \\
        -r TEMPLATE \\
        -t /tmp/badRegistration/caseid_FA1Warp.nii.gz /tmp/badRegistration/caseid_FA0GenericAffine.mat
    
Finally, if wanted, you can copy the transform files to {args.xfrmDir}/ directory.

Note: Replace all the above directories with absolute paths.\n\n''')

        while input('Press Enter when you are done with QC/re-registration: '):
            pass

        allFAdata, cumsumFA = calc_mean(imgs, targetData.shape, args.qc)

    meanFAdata = cumsumFA / len(imgs)
    meanFA = pjoin(args.statsDir, 'mean_FA.nii.gz')

    # outDir should contain
    # all_{modality}.nii.gz
    # mean_FA.nii.gz
    # mean_FA_mask.nii.gz
    # mean_FA_skeleton.nii.gz
    # mean_FA_skeleton_mask.nii.gz
    # mean_FA_skeleton_mask_dst.nii.gz

    if args.modality == 'FA':

        if not args.templateMask:
            print('Creating template mask ...')
            args.templateMask = pjoin(args.statsDir, 'mean_FA_mask.nii.gz')
            meanFAmaskData = (meanFAdata > 0) * 1
            save_nifti(args.templateMask, meanFAmaskData.astype('uint8'),
                       target.affine, target.header)

        else:
            meanFAmaskData = load(args.templateMask).get_data()

        meanFAdata = meanFAdata * meanFAmaskData
        save_nifti(meanFA, meanFAdata, target.affine, target.header)

        # if skeleton is not given:
        #     create all three of skeleton, skeletonMask, and skeletonMaskDst

        # if skeleton is given and (neither skeletonMask nor skeletonMaskDst is given):
        #     create skeletonMask and skeletonMaskDst

        # if skeleton and skeletonMask is given and skeletonMaskDst is not given:
        #     create skeletonMaskDst

        if not args.skeleton:
            print(
                'Creating all three of skeleton, skeletonMask, and skeletonMaskDst ...'
            )
            args.skeleton = pjoin(args.statsDir, 'mean_FA_skeleton.nii.gz')
            args.skeletonMask = pjoin(args.statsDir,
                                      'mean_FA_skeleton_mask.nii.gz')
            args.skeletonMaskDst = pjoin(args.statsDir,
                                         'mean_FA_skeleton_mask_dst.nii.gz')

            _create_skeleton(meanFA, args.skeleton)
            _create_skeletonMask(args.skeleton, args.SKEL_THRESH,
                                 args.skeletonMask)
            _create_skeletonMaskDst(args.templateMask, args.skeletonMask,
                                    args.skeletonMaskDst)

        if args.skeleton and not (args.skeletonMask or args.skeletonMaskDst):
            print('Creating skeletonMask and skeletonMaskDst ...')
            args.skeletonMask = pjoin(args.statsDir,
                                      'mean_FA_skeleton_mask.nii.gz')
            args.skeletonMaskDst = pjoin(args.statsDir,
                                         'mean_FA_skeleton_mask_dst.nii.gz')

            _create_skeletonMask(args.skeleton, args.SKEL_THRESH,
                                 args.skeletonMask)
            _create_skeletonMaskDst(args.templateMask, args.skeletonMask,
                                    args.skeletonMaskDst)

        if args.skeleton and not args.skeletonMask and args.skeletonMaskDst:
            print('Creating skeletonMask ...')
            args.skeletonMask = pjoin(args.statsDir,
                                      'mean_FA_skeleton_mask.nii.gz')

            _create_skeletonMask(args.skeleton, args.SKEL_THRESH,
                                 args.skeletonMask)

        if (args.skeleton and args.skeletonMask) and not args.skeletonMaskDst:
            print('Creating skeletonMaskDst ...')
            args.skeletonMaskDst = pjoin(args.statsDir,
                                         'mean_FA_skeleton_mask_dst.nii.gz')

            _create_skeletonMaskDst(args.templateMask, args.skeletonMask,
                                    args.skeletonMaskDst)

    # mask allFA, this step does not seem to have any effect on the pipeline, it should help the user to visualize only
    if args.qc:
        check_call(
            (' ').join(['fslmaths', allFA, '-mas', args.templateMask, allFA]),
            shell=True)

    # projecting all {modality} data onto skeleton
    pool = Pool(args.ncpu)
    for c, imgPath in zip(cases, imgs):
        pool.apply_async(project_skeleton, (c, imgPath, args, skelDir),
                         error_callback=RAISE)

    pool.close()
    pool.join()

    if not args.noAllSkeleton:

        allFAskeletonized = pjoin(args.statsDir,
                                  f'all_{args.modality}_skeletonized.nii.gz')
        print('Creating ', allFAskeletonized)

        # this loop has been moved out of multiprocessing block to prevent memroy error
        allFAskeletonizedData = np.zeros((len(imgs), X, Y, Z), dtype='float32')
        for i, c in enumerate(cases):
            allFAskeletonizedData[i, :] = load(
                pjoin(
                    skelDir,
                    f'{c}_{args.modality}_to_target_skel.nii.gz')).get_data()

        save_nifti(allFAskeletonized, np.moveaxis(allFAskeletonizedData, 0,
                                                  -1), target.affine,
                   target.header)
        print(
            f'Created {allFAskeletonized} and corresponding index file: {seqFile}'
        )

    return args