def main(self): with TemporaryDirectory() as tmpdir: tmpdir = local.path(tmpdir) shortdwi = tmpdir / 'dwiShort.nii.gz' shortmask = tmpdir / 'maskShort.nii.gz' tmpdwi = tmpdir / 'dwi.nhdr' tmpdwimask = tmpdir / 'dwimask.nhdr' # TODO when UKFTractography supports float32, it should be removed # typecast to short short = load_nifti(self.dwi._path) save_nifti(shortdwi._path, short.get_data().astype('int16'), short.affine, short.header) short = load_nifti(self.dwimask._path) save_nifti(shortmask._path, short.get_data().astype('int16'), short.affine, short.header) # convert the dwi to NRRD nhdr_write(shortdwi._path, self.bvalFile._path, self.bvecFile._path, tmpdwi._path) # convert the mask to NRRD nhdr_write(shortmask._path, None, None, tmpdwimask._path) key_val_pair = [] if self.givenParams: key_val_pair = self.givenParams.split(',') for i in range( 0, len(ukfdefaults) - 1, 2 ): # -1 to pass --recordTensors which is always a default try: ind = key_val_pair.index(ukfdefaults[i]) ukfdefaults[i + 1] = key_val_pair[ind + 1] # since ukfdefault[i+1] has been already replaced by key_val_pair[ind+1] # remove key_val_pair[ind] and [ind+1] to prevent double specification key_val_pair[ind:ind + 2] = [] except ValueError: pass params = [ '--dwiFile', tmpdwi, '--maskFile', tmpdwimask, '--seedsFile', tmpdwimask, '--tracts', self.out ] + list(ukfdefaults) + key_val_pair logging.info('Peforming UKF tractography of {}'.format(tmpdwi)) UKFTractography[params] & FG
def main(self): prefix = self.img.name.split('.')[0] directory = self.img.parent if self.out is None: self.out = os.path.join(directory, prefix) # bet changed in FSL 6.0.1, it creates a mask for every volume in 4D # if the image is 4D, baseline image should be extracted first dim = load_nifti(self.img._path).header['dim'][0] if dim == 4: if not self.bval_file: self.bval_file = os.path.join(directory, prefix + '.bval') bet_mask(self.img._path, self.out, 4, self.bval_file, thr=self.bet_threshold) else: bet_mask(self.img._path, self.out, 3, thr=self.bet_threshold)
def maskfilter(maskPath, scale, filtered_maskPath): ''' This python executable replicates the functionality of https://github.com/MRtrix3/mrtrix3/blob/master/core/filter/mask_clean.h It performs a few erosion and dilation to remove islands of non-brain region in a brain mask. ''' mask = load_nifti(maskPath) filtered_mask = single_scale(mask.get_fdata(), scale) save_nifti(filtered_maskPath, filtered_mask, mask.affine, mask.header)
def fuseWeightedAvg(labels, weights, out, target_header): # for each label, fuse warped labelmaps to compute output labelmap print("Apply weights to warped training {} et al., fuse, and threshold".format(labels[0])) data= np.zeros(target_header['dim'][1:4], dtype= 'float32') for label, w in zip(labels, weights): data+= load_nifti(label._path).get_data()*w # out is {labelname}.nii.gz save_nifti(out, ((data>0.5)*1).astype('uint8'), target_header.get_best_affine(), target_header) print("Made labelmap: " + out)
def fuseAvg(labels, out, target_header): from plumbum.cmd import AverageImages with TemporaryDirectory() as tmpdir: nii = local.path(tmpdir) / 'avg.nii.gz' AverageImages['3', nii, '0', labels] & FG img= load_nifti(nii._path) # Binary operation, if out>0.5, pipe the output and save as {labelname}.nii.gz # out is {labelname}.nii.gz save_nifti(out, ((img.get_data()>0.5)*1).astype('uint8'), target_header.get_best_affine(), target_header) print("Made labelmap: " + out)
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)
def fuseWeightedAvg(labels, weights, out): # for each label, fuse warped labelmaps to compute output labelmap print("Apply weights to warped training {} et al., fuse, and threshold".format(labels[0])) init= True for label, w in zip(labels, weights): img= load_nifti(label) if init: data=img.get_data()*w affine= img.affine init= False else: data+=img.get_data()*w # out is {labelname}.nii.gz save_nifti(out, ((data>0.5)*1).astype('uint8'), affine, img.header) print("Made labelmap: " + out)
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)
def main(self): with tempfile.TemporaryDirectory() as tmpdir: tmpdir = local.path(tmpdir) b0masked = tmpdir / "b0masked.nii.gz" # Sylvain wants both b0maskedbrain = tmpdir / "b0maskedbrain.nii.gz" t2masked = tmpdir / 't2masked.nii.gz' print('Masking the T2') ImageMath(3, t2masked, 'm', self.t2, self.t2mask) brain = tmpdir / "brain.nii.gz" wmparc = tmpdir / "wmparc.nii.gz" brainmgz = self.parent.fsdir / 'mri/brain.mgz' wmparcmgz = self.parent.fsdir / 'mri/wmparc.mgz' wmparcindwi = tmpdir / 'wmparcInDwi.nii.gz' # Sylvain wants both wmparcinbrain = tmpdir / 'wmparcInBrain.nii.gz' print( "Making brain.nii.gz and wmparc.nii.gz from their mgz versions" ) vol2vol = local[self.parent.fshome / 'bin/mri_vol2vol'] label2vol = local[self.parent.fshome / 'bin/mri_label2vol'] with local.env(SUBJECTS_DIR=''): vol2vol('--mov', brainmgz, '--targ', brainmgz, '--regheader', '--o', brain) label2vol('--seg', wmparcmgz, '--temp', brainmgz, '--regheader', wmparcmgz, '--o', wmparc) print('Extracting B0 from DWI and masking it') check_call((' ').join([ pjoin(FILEDIR, 'bse.py'), '-i', self.parent.dwi, '-m', self.parent.dwimask, '-o', b0masked ]), shell=True) print('Made masked B0') # rigid registration from t2 to brain.nii.gz pre = tmpdir / 'BrainToT2' BrainToT2Affine = pre + '0GenericAffine.mat' print('Computing rigid registration from brain.nii.gz to t2') rigid_registration(3, brain, t2masked, pre) # generates three files for rigid registration: # pre0GenericAffine.mat preInverseWarped.nii.gz preWarped.nii.gz # generates five files for default(rigid+affine+deformable syn) registration: # pre0GenericAffine.mat pre1Warp.nii.gz preWarped.nii.gz pre1InverseWarp.nii.gz preInverseWarped.nii.gz dwi_res = load_nifti( str(b0masked)).header['pixdim'][1:4].round(decimals=2) brain_res = load_nifti( str(brain)).header['pixdim'][1:4].round(decimals=2) print(f'DWI resolution: {dwi_res}') print(f'FreeSurfer brain resolution: {brain_res}') print('Registering wmparc to B0 through T2') registerFs2Dwi_T2(tmpdir, 'fsbrainToT2ToB0', b0masked, t2masked, BrainToT2Affine, wmparc, wmparcindwi) if (dwi_res != brain_res).any(): print( 'DWI resolution is different from FreeSurfer brain resolution' ) print( 'wmparc wil be registered to both DWI and brain resolution' ) print( 'Check output files wmparcInDwi.nii.gz and wmparcInBrain.nii.gz' ) print('Resampling B0 to brain resolution') ResampleImageBySpacing('3', b0masked, b0maskedbrain, brain_res.tolist()) print('Registering wmparc to resampled B0') registerFs2Dwi_T2(tmpdir, 'fsbrainToT2ToResampledB0', b0maskedbrain, t2masked, BrainToT2Affine, wmparc, wmparcinbrain) # copying images to outDir b0masked.copy(self.parent.out) wmparcindwi.copy(self.parent.out) if b0maskedbrain.exists(): b0maskedbrain.copy(self.parent.out) wmparcinbrain.copy(self.parent.out) if self.parent.debug: tmpdir.copy(self.parent.out, 'fs2dwi-debug-' + str(os.getpid())) print('See output files in ', self.parent.out._path)
def main(self): with tempfile.TemporaryDirectory() as tmpdir: tmpdir = local.path(tmpdir) b0masked = tmpdir / "b0masked.nii.gz" # Sylvain wants both b0maskedbrain = tmpdir / "b0maskedbrain.nii.gz" brain = tmpdir / "brain.nii.gz" wmparc = tmpdir / "wmparc.nii.gz" brainmgz = self.parent.fsdir / 'mri/brain.mgz' wmparcmgz = self.parent.fsdir / 'mri/wmparc.mgz' wmparcindwi = tmpdir / 'wmparcInDwi.nii.gz' # Sylvain wants both wmparcinbrain = tmpdir / 'wmparcInBrain.nii.gz' print( "Making brain.nii.gz and wmparc.nii.gz from their mgz versions" ) vol2vol = local[self.parent.fshome / 'bin/mri_vol2vol'] label2vol = local[self.parent.fshome / 'bin/mri_label2vol'] with local.env(SUBJECTS_DIR=''): vol2vol('--mov', brainmgz, '--targ', brainmgz, '--regheader', '--o', brain) label2vol('--seg', wmparcmgz, '--temp', brainmgz, '--regheader', wmparcmgz, '--o', wmparc) print('Extracting B0 from DWI and masking it') check_call((' ').join([ pjoin(FILEDIR, 'bse.py'), '-i', self.parent.dwi, '-m', self.parent.dwimask, '-o', b0masked ]), shell=True) print('Made masked B0') dwi_res = load_nifti( str(b0masked)).header['pixdim'][1:4].round(decimals=2) brain_res = load_nifti( str(brain)).header['pixdim'][1:4].round(decimals=2) print(f'DWI resolution: {dwi_res}') print(f'FreeSurfer brain resolution: {brain_res}') print('Registering wmparc to B0') registerFs2Dwi(tmpdir, 'fsbrainToB0', b0masked, brain, wmparc, wmparcindwi) if (dwi_res != brain_res).any(): print( 'DWI resolution is different from FreeSurfer brain resolution' ) print( 'wmparc wil be registered to both DWI and brain resolution' ) print( 'Check output files wmparcInDwi.nii.gz and wmparcInBrain.nii.gz' ) print('Resampling B0 to brain resolution') ResampleImageBySpacing('3', b0masked, b0maskedbrain, brain_res.tolist()) print('Registering wmparc to resampled B0') registerFs2Dwi(tmpdir, 'fsbrainToResampledB0', b0maskedbrain, brain, wmparc, wmparcinbrain) # copying images to outDir b0masked.copy(self.parent.out) wmparcindwi.copy(self.parent.out) if b0maskedbrain.exists(): b0maskedbrain.copy(self.parent.out) wmparcinbrain.copy(self.parent.out) if self.parent.debug: tmpdir.copy(self.parent.out, 'fs2dwi-debug-' + str(os.getpid())) print('See output files in ', self.parent.out._path)
def main(self): with TemporaryDirectory() as tmpdir: tmpdir = local.path(tmpdir) shortdwi = tmpdir / 'dwiShort.nii.gz' shortmask = tmpdir / 'maskShort.nii.gz' tmpdwi = tmpdir / 'dwi.nhdr' tmpdwimask = tmpdir / 'dwimask.nhdr' # TODO when UKFTractography supports float32, it should be removed # typecast to short short= load_nifti(self.dwi._path) save_nifti(shortdwi._path, short.get_data().astype('int16'), short.affine, short.header) short= load_nifti(self.dwimask._path) save_nifti(shortmask._path, short.get_data().astype('int16'), short.affine, short.header) if self.bhigh: from conversion import grad_remove bhigh_prefix= tmpdir / self.dwi.stem+ f'_bhigh_{self.bhigh}' bhigh_dwi= bhigh_prefix+'.nii.gz' grad_remove(shortdwi._path, bhigh_dwi, interval=[int(self.bhigh)+50,1e6], bvalFile=self.bvalFile, bvecFile=self.bvecFile) shortdwi= local.path(bhigh_dwi) self.bvalFile = local.path(bhigh_prefix + '.bval') self.bvecFile = local.path(bhigh_prefix + '.bvec') # preserve the filtered attributes in case the user wants to run UKFTractography separately in future shortdwi.copy(self.dwi.dirname) self.bvalFile.copy(self.dwi.dirname) self.bvecFile.copy(self.dwi.dirname) # convert the dwi to NRRD nhdr_write(shortdwi._path, self.bvalFile._path, self.bvecFile._path, tmpdwi._path) # convert the mask to NRRD nhdr_write(shortmask._path, None, None, tmpdwimask._path) key_val_pair=[] if self.givenParams: key_val_pair= self.givenParams.split(',') for i in range(0,len(ukfdefaults)-1,2): # -1 to pass --recordTensors which is always a default try: ind= key_val_pair.index(ukfdefaults[i]) ukfdefaults[i + 1] = key_val_pair[ind + 1] # since ukfdefault[i+1] has been already replaced by key_val_pair[ind+1] # remove key_val_pair[ind] and [ind+1] to prevent double specification key_val_pair[ind:ind+2]=[] except ValueError: pass params = ['--dwiFile', tmpdwi, '--maskFile', tmpdwimask, '--seedsFile', tmpdwimask, '--tracts', self.out] + list(ukfdefaults) + key_val_pair logging.info('Peforming UKF tractography of {}'.format(tmpdwi)) UKFTractography[params] & FG
def main(self): if self.img_file.endswith('.nii') or self.img_file.endswith('.nii.gz'): mri = load_nifti(self.img_file._path) else: print('Invalid image format, accepts nifti only') exit(1) hdr = mri.header dim = hdr['dim'][0] if dim == 4: if not self.bvec_file and not self.bval_file: print('bvec and bvals files not specified, exiting ...') exit(1) elif dim == 3: spcdir_new = axis_align_3d(hdr) else: print('Invalid image dimension, has to be either 3 or 4') offset_orig = matrix(hdr.get_best_affine()[0:3, 3]).T spcdir_orig = hdr.get_best_affine()[0:3, 0:3] if self.axisAlign and not self.center: # pass spcdir_new and offset_orig if not self.out_prefix: self.out_prefix = self.img_file.split('.')[ 0] + '-ax' # a clever way to get prefix including path if dim == 4: spcdir_new = axis_align_dwi(hdr, self.bvec_file, self.bval_file, self.out_prefix) hdr_out = update_hdr(hdr, spcdir_new, offset_orig) elif not self.axisAlign and self.center: # pass spcdir_orig and offset_new if not self.out_prefix: self.out_prefix = self.img_file.split('.')[ 0] + '-ce' # a clever way to get prefix including path offset_new = -spcdir_orig @ matrix((hdr['dim'][1:4] - 1) / 2).T hdr_out = update_hdr(hdr, spcdir_orig, offset_new) # rename the bval file self.bval_file.copy(self.out_prefix + '.bval') # rename the bvec file self.bvec_file.copy(self.out_prefix + '.bvec') else: # self.axisAlign and self.center: # pass spcdir_new and offset_new if not self.out_prefix: self.out_prefix = self.img_file.split('.')[ 0] + '-xc' # a clever way to get prefix including path if dim == 4: spcdir_new = axis_align_dwi(hdr, self.bvec_file, self.bval_file, self.out_prefix) offset_new = -spcdir_new @ matrix((hdr['dim'][1:4] - 1) / 2).T hdr_out = update_hdr(hdr, spcdir_new, offset_new) # write out the modified image save_nifti(self.out_prefix + '.nii.gz', mri.get_data(), hdr_out.get_best_affine(), hdr_out)
def makeAtlases(target, trainingTable, outPrefix, fusion, threads, debug): with TemporaryDirectory() as tmpdir: tmpdir = local.path(tmpdir) L= len(trainingTable) multiDataFrame= pd.concat([trainingTable, pd.DataFrame({'tmpdir': [tmpdir]*L, 'target': [str(target)]*L})], axis= 1) logging.info('Create {} atlases: compute transforms from images to target and apply over images'.format(L)) pool = multiprocessing.Pool(threads) # Use all available cores, otherwise specify the number you want as an argument pool.map_async(train2target, multiDataFrame.iterrows()) pool.close() pool.join() logging.info('Fuse warped labelmaps to compute output labelmaps') atlasimages = tmpdir // 'atlas*.nii.gz' # sorting is required for applying weight to corresponding labelmap atlasimages.sort() if fusion.lower() == 'wavg': ALPHA_DEFAULT= 0.45 logging.info('Compute MI between warped images and target') pool = multiprocessing.Pool(threads) for img in atlasimages: print('MI between {} and target'.format(img)) miFile= img+'.txt' pool.apply_async(func= computeMI, args= (target, img, miFile, )) pool.close() pool.join() mis= [] with open(tmpdir+'/MI.txt','w') as fw: for img in atlasimages: with open(img+'.txt') as f: mi= f.read().strip() fw.write(img+','+mi+'\n') mis.append(float(mi)) weights = weightsFromMIExp(mis, ALPHA_DEFAULT) target_header= load_nifti(target._path).header pool = multiprocessing.Pool(threads) # Use all available cores, otherwise specify the number you want as an argument for labelname in list(trainingTable)[1:]: # list(d) gets column names out = os.path.abspath(outPrefix+ f'_{labelname}.nii.gz') if os.path.exists(out): os.remove(out) labelmaps = tmpdir // (labelname + '*') labelmaps.sort() if fusion.lower() == 'avg': print(' ') # parellelize # fuseAvg(labelmaps, out, target_header) pool.apply_async(func= fuseAvg, args= (labelmaps, out, target_header, )) elif fusion.lower() == 'antsjointfusion': print(' ') # atlasimages are the warped images # labelmaps are the warped labels # parellelize # fuseAntsJointFusion(target, atlasimages, labelmaps, out) pool.apply_async(func= fuseAntsJointFusion, args= (target, atlasimages, labelmaps, out, )) elif fusion.lower() == 'wavg': print(' ') # parellelize # fuseWeightedAvg(labelmaps, weights, out, target_header) pool.apply_async(func= fuseWeightedAvg, args= (labelmaps, weights, out, target_header, )) else: print('Unrecognized fusion option: {}. Skipping.'.format(fusion)) pool.close() pool.join() if debug: tmpdir.copy(pjoin(dirname(outPrefix), 'atlas-debug-' + str(os.getpid())))
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): 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')
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')