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