def create_dfnd_mask(refimg, affine_func_2_anat, affine_anat_2_atlas, warp_anat_2_atlas, reference): import os from p3.utility import get_basename # get the current node directory cwd = os.getcwd() # get filename to output out_file = os.path.join(cwd, '{}_atlas.nii.gz'.format(get_basename(refimg))) mask_file = os.path.join( cwd, '{}_atlas_dfnd.nii.gz'.format(get_basename(refimg))) # create dfnd mask command = 'antsApplyTransforms -f 0.0 -d 3 -o {} -i {} -t {} -t {} -t {} -r {} -v'.format( out_file, refimg, warp_anat_2_atlas, affine_anat_2_atlas, affine_func_2_anat, reference) print(command) os.system(command) # convert to binary os.system('fslmaths {} -bin {}'.format(out_file, mask_file)) return mask_file
def format_reference(func, reference, bids_dir): import os import nibabel from p3.utility import get_basename from bids.grabbids import BIDSLayout # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # get filename to output formatted_reference = os.path.join( cwd, '{}_format4D.nii.gz'.format(get_basename(func))) # get dim 4 and TR of input image dim4 = nibabel.load(func).header.get_data_shape()[3] # get the 4th dim TR = BIDSLayout(bids_dir).get_metadata(func)[ 'RepetitionTime'] # get the TR # make the reference image the same dims as the input print('Formatting reference image...') command = 'ImageMath 3 {} ReplicateImage {} {} {} 0'.format( formatted_reference, reference, dim4, TR) print(command) os.system(command) return (formatted_reference, dim4, TR)
def applytransforms(in_file, reference4D, combined_transforms4D, warp_func_2_refimg, dfnd_mask): import os from p3.utility import get_basename # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # get filename to output out_file = os.path.join( cwd, '{}_moco_atlas.nii.gz'.format(get_basename(in_file))) # set up command to run command = 'antsApplyTransforms -f 0.0 -d 4 -i {} -r {} -o {} -t {} -t {} -v'.format( in_file, reference4D, out_file, combined_transforms4D, warp_func_2_refimg) print(command) # apply transforms os.system(command) # mask the aligned func with the dfnd mask print('Applying dfnd mask...') os.system('fslmaths {0} -mul {1} {0}'.format(out_file, dfnd_mask)) # return moco, atlas-aligned functional image return out_file
def convertvsm2ANTSwarp(in_file, ped): """ Convert the voxel shift map to ants warp Grabbed this from fmriprep (https://github.com/poldracklab/fmriprep/blob/master/fmriprep/interfaces/itk.py#L178) """ import nibabel as nb import numpy as np import os from p3.utility import get_basename # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # load file nii = nb.load(in_file) phaseEncDim = {'x': 0, 'y': 1, 'z': 2}[ped[0]] if len(ped) == 2: phaseEncSign = 1.0 else: phaseEncSign = -1.0 # Fix header hdr = nii.header.copy() hdr.set_data_dtype(np.dtype('<f4')) hdr.set_intent('vector', (), '') # Get data, convert to mm data = nii.get_data() aff = np.diag([1.0, 1.0, -1.0]) if np.linalg.det(aff) < 0 and phaseEncDim != 0: # Reverse direction since ITK is LPS aff *= -1.0 aff = aff.dot(nii.affine[:3, :3]) # scale the data correctly data *= phaseEncSign * nii.header.get_zooms()[phaseEncDim] # Add missing dimensions zeros = np.zeros_like(data) field = [zeros, zeros] field.insert(phaseEncDim, data) field = np.stack(field, -1) # Add empty axis field = field[:, :, :, np.newaxis, :] # Write out out_file = os.path.join(cwd, '{}_antswarp.nii.gz'.format(get_basename(in_file))) nb.Nifti1Image(field.astype(np.dtype('<f4')), nii.affine, hdr).to_filename(out_file) return out_file
def combinetransforms(func, reference, dim4, TR, affine_func_2_anat, affine_anat_2_atlas, warp_anat_2_atlas, warp_fmc=None): import os from p3.utility import get_basename # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # get filename to output name = get_basename(func) combined_transforms = os.path.join( cwd, '{}_combined_transforms.nii.gz'.format(name)) combined_transforms4D = os.path.join( cwd, '{}_combined_transforms4D.nii.gz'.format(name)) # set up transforms (check in field map correction files exist) # we exclude the func_2_refimg transform since it is already 4D if warp_fmc: transforms = '-t {} -t {} -t {} -t {}'.format( warp_anat_2_atlas, affine_anat_2_atlas, affine_func_2_anat, warp_fmc, ) else: transforms = '-t {} -t {} -t {}'.format( warp_anat_2_atlas, affine_anat_2_atlas, affine_func_2_anat, ) # convert the transforms to 4D print('Combining transforms into one warp displacement field...') command = 'antsApplyTransforms -f 0.0 -d 3 -o [{},1] {} -r {} -v'.format( combined_transforms, transforms, reference) print(command) os.system(command) # replicate the combined transform print('Replicating the combined transform into 4D...') command = 'ImageMath 3 {} ReplicateDisplacement {} {} {} 0'.format( combined_transforms4D, combined_transforms, dim4, TR) print(command) os.system(command) # return the 4D combined transform return combined_transforms4D
def resample_2_epi(T1, epi, aparc_aseg=None): """ Resample images to epi resolution """ import os import shutil from p3.utility import get_basename # get cwd cwd = os.getcwd() # get first epi from list epi = epi[0] # get filename of T1 out_file = os.path.join(cwd, '{}_funcres.nii.gz'.format(get_basename(T1))) # resample the T1 os.system('3dresample -rmode Li -master {} -prefix {} -inset {}'.format( epi, out_file, T1)) T1_epi = out_file # ONLY if aparc_aseg defined if aparc_aseg: # get filename of aparc_aseg out_file = os.path.join( cwd, '{}_funcres.nii.gz'.format(get_basename(aparc_aseg))) # resample the aparc_aseg os.system( '3dresample -rmode NN -master {} -prefix {} -inset {}'.format( epi, out_file, aparc_aseg)) aparc_aseg_epi = out_file else: # set to empty aparc_aseg_epi = '' # return resampled images return (T1_epi, aparc_aseg_epi)
def antsMotionCorr(fixed_image, moving_image, transform, writewarp): import os from p3.utility import get_basename # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # strip filename extension name = get_basename(moving_image) output_basename = os.path.join(cwd, name) # set the output basename output_mocoparams = os.path.join(cwd, '{}MOCOparams.csv'.format(name)) output_warp = os.path.join(cwd, '{}Warp.nii.gz'.format(name)) output_warpedimg = os.path.join(cwd, '{}_Warped.nii.gz'.format(name)) # check write warp boolean if writewarp: writewarp = 1 else: writewarp = 0 # setup commandline execution command = 'antsMotionCorr -d 3 -o [{},{}] -m MI[{},{},{},{},{},{}] -t {}[{}] -u 1 -e 1 ' \ '-s {} -f {} -i {} -n 1 -w {} -v'.format( output_basename, output_warpedimg, fixed_image, moving_image, 1, # metric weight 32, # number of bins 'Regular', # sampling Strategy 0.2, # sampling percentage transform, 0.1, # gradient step '1x0', # smoothing sigmas '2x1', # shrink factors '20x5', # iterations, writewarp # set flag for writing warps ) print(command) # print command before running # run antsMotionCorr os.system(command) # remove the InverseWarp image to save space os.system('rm {}'.format(os.path.join(cwd, '*InverseWarp.nii.gz'))) # return the outputs return (output_warp, output_mocoparams, output_warpedimg)
def avganats(anat_list): import os from p3.utility import get_basename # get current path path = os.getcwd() # join list into string filelist = ' '.join(anat_list) # get filename of first file outfile = '{}_avg.nii.gz'.format(get_basename(anat_list[0])) os.system('3dMean -prefix {} {}'.format(outfile, filelist)) # return avg anat return os.path.join(path, outfile)
def fsl_prepare_fieldmap(phasediff, magnitude, TE): import os from p3.utility import get_basename # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # get filename to output out_file = os.path.join( cwd, '{}_fieldmap.nii.gz'.format(get_basename(phasediff))) # run prepare field map os.system('fsl_prepare_fieldmap SIEMENS {} {} {} {}'.format( phasediff, magnitude, out_file, TE)) # return the fieldmap file return out_file
def apply_warp(in_file, reference, transform): import os from p3.utility import get_basename # get cwd cwd = os.getcwd() # get filename to output out_file = os.path.join(cwd, '{}_atlas.nii.gz'.format(get_basename(in_file))) # set up command to run command = 'antsApplyTransforms -f 0.0 -d 3 -i {} -r {} -o {} -t {} -v'.format( in_file, reference, out_file, transform) print(command) # run transform os.system(command) # return output return out_file
def combinetransforms(avgepi, reference, unwarp, realign): import os from p3.utility import get_basename # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # get filename to output combined_transforms = os.path.join( cwd, '{}_unwarpedtransform.nii.gz'.format(get_basename(avgepi))) # set up transforms transforms = '-t {} -t {}'.format(realign, unwarp) # convert the transforms to 4D print('Combining transforms into one warp displacement field...') command = 'antsApplyTransforms -f 0.0 -d 3 -o [{},1] {} -r {} -v'.format( combined_transforms, transforms, reference) print(command) os.system(command) # return the 4D combined transform return combined_transforms
def get_prefix(filename): from p3.utility import get_basename return '{}_'.format(get_basename(filename))
def calcFD(moco_params, brain_radius, threshold, filtered_threshold, TR, min_bpm, max_bpm): import os import math from p3.utility import get_basename import numpy as np from scipy import signal def respiration_iirnotch(TR_in_sec, bpm_min=18.582, bpm_max=25.7263): """ Function for calculating filter parameters for respiration filter Takes in the TR (optional min/max breaths-per-min, bpm_min, bpm_max). Returns the parameters for IIR Notch filter. """ fs = 1.0 / TR_in_sec # Sampling frequency (Hz) fn = fs / 2.0 # Nyquist frequency (Hz) # RR MIN rr_min = bpm_min / 60.0 # respiration rate minimum in Hz fa_min = abs(rr_min - math.floor( (rr_min + fn) / fs) * fs) # Aliased minimum frequency (Hz) w0_min = fa_min / fn # Normalized minimum frequency # RR MAX rr_max = bpm_max / 60.0 # respiration rate maximum in Hz fa_max = abs(rr_max - math.floor( (rr_max + fn) / fs) * fs) # Aliased maximum frequency (Hz) w0_max = fa_max / fn # Normalized maximum frequency # RR iirnotch filter w0 = np.mean([w0_min, w0_max]) # Mean normalized frequency bw = abs(w0_max - w0_min) # Normalized bandwidth Q = w0 / bw # Quality factor b, a = signal.iirnotch(w0, Q) # Filter design return b, a # save to node folder (go up 2 directories bc of iterfield) cwd = os.path.dirname(os.path.dirname(os.getcwd())) # set output filename out_file = os.path.join(cwd, '{}.FD'.format(get_basename(moco_params))) tmask_out_file = os.path.join(cwd, '{}.tmask'.format(get_basename(moco_params))) filt_moco_file = os.path.join( cwd, '{}_filtered.1D'.format(get_basename(moco_params))) filt_out_file = os.path.join( cwd, '{}_filtered.FD'.format(get_basename(moco_params))) filt_tmask_out_file = os.path.join( cwd, '{}_filtered.tmask'.format(get_basename(moco_params))) # open file with open(moco_params, 'r') as moco_file: moco_nums = moco_file.readlines() # format the moco numbers from strings to float f_moco_nums = [ tuple(map(float, list(filter(bool, moco_num.rstrip().split(" "))))) for moco_num in moco_nums ] fr_moco_nums = [(f_moco_num[0] * brain_radius * math.pi / 180, f_moco_num[1] * brain_radius * math.pi / 180, f_moco_num[2] * brain_radius * math.pi / 180, f_moco_num[3], f_moco_num[4], f_moco_num[5]) for f_moco_num in f_moco_nums] # convert to numpy array and filter the motion numbers np_moco_nums = np.array(f_moco_nums) TR = float(TR) # convert TR to float b, a = respiration_iirnotch(TR, min_bpm, max_bpm) # create filter # filter data (run twice for 4th order) filt_moco_stage1 = signal.filtfilt(b, a, np_moco_nums, axis=0, padtype=None) filt_moco_stage2 = signal.filtfilt(b, a, filt_moco_stage1, axis=0, padtype=None) filt_moco = filt_moco_stage2 # Convert rotations to mm filt_moco[:, 4:7] = filt_moco[:, 4:7] * brain_radius * math.pi / 180 # Calculate FD values for the series filtered_FD = np.array([ np.concatenate( (np.array([0]), np.sum(np.absolute(filt_moco[1:, :] - filt_moco[0:-1, :]), axis=1)), axis=0) ]).transpose() # convert to lists filt_moco = np.ndarray.tolist(filt_moco) filtered_FD = [FD_val[0] for FD_val in np.ndarray.tolist(filtered_FD)] # calculate FD FD = [0] for f1, f2 in zip(fr_moco_nums[0:-1], fr_moco_nums[1:]): FD.append(sum(map(abs, [v1 - v2 for v1, v2 in zip(f1, f2)]))) # write FD to file with open(out_file, 'w') as FD_file: for val in FD: FD_file.write(str(val)) FD_file.write('\n') # write tmask to file with open(tmask_out_file, 'w') as tmask_file: for val in FD: tmask_file.write(str(int(val < threshold))) tmask_file.write('\n') # write filtered motion numbers to file with open(filt_moco_file, 'w') as fm_file: for val in filt_moco: fm_file.write(' '.join([str(v) for v in val])) fm_file.write('\n') # write filtered FD to file with open(filt_out_file, 'w') as filt_FD_file: for val in filtered_FD: filt_FD_file.write(str(val)) filt_FD_file.write('\n') # write filtered tmask to file with open(filt_tmask_out_file, 'w') as filt_tmask_file: for val in filtered_FD: filt_tmask_file.write(str(int(val < filtered_threshold))) filt_tmask_file.write('\n') # return the FD file return out_file, tmask_out_file, filt_moco_file, filt_out_file, filt_tmask_out_file
def gett1name(T1): from p3.utility import get_basename return get_basename(T1)
def __init__(self, settings): # call base constructor super().__init__(settings) # define input/output node self.set_input([ 'func', # I pass this in so I can get the TR info from BIDS 'func_stc_despike', 'warp_func_2_refimg', 'warp_fmc', 'refimg', 'affine_func_2_anat', 'warp_func_2_anat', 'affine_anat_2_atlas', 'warp_anat_2_atlas' ]) self.set_output(['func_atlas']) # define datasink substitutions atlas_base = get_basename(settings['atlas']) # get atlas basename self.set_subs([ ('_flirt', '_funcres'), (atlas_base, 'atlas/{}'.format(atlas_base) ) # save resampled atlas to atlas folder ]) self.set_resubs([ ('sub-(?P<subject>\w+_)', 'func/sub-\g<subject>'), # place file under anat folder ('func/sub-(?P<subject>\w+)_ses-(?P<session>\w+)_', 'func/ses-\g<session>/sub-\g<subject>_ses-\g<session>_' ) # add session folders if they exist ]) # grab the resolution of the refimg self.get_resolution = Node(Function(input_names=['reference'], output_names=['resolution'], function=get_resolution), name='get_resolution') # resample atlas to epi space so we can use it as a reference self.resample = Node(fsl.FLIRT(no_search=True), name='resample') self.resample.inputs.in_file = set_atlas_path(settings['atlas']) self.resample.inputs.reference = set_atlas_path(settings['atlas']) # format the reference image (which should be the resampled atlas) self.format_reference = MapNode(Function( input_names=['func', 'reference', 'bids_dir'], output_names=['formatted_reference', 'dim4', 'TR'], function=format_reference), iterfield=['func'], name='format_reference') self.format_reference.inputs.bids_dir = settings['bids_dir'] # combine 3D transforms and replicate to 4D self.combinetransforms = MapNode( Function(input_names=[ 'func', 'reference', 'dim4', 'TR', 'affine_func_2_anat', 'affine_anat_2_atlas', 'warp_anat_2_atlas', 'warp_fmc' ], output_names=['combined_transforms4D'], function=combinetransforms), iterfield=['func', 'dim4', 'TR', 'warp_fmc'], name='combinetransforms') self.combinetransforms.n_procs = settings['num_threads'] # align the reference image to atlas and create a dfnd mask from it self.create_dfnd_mask = Node(Function(input_names=[ 'refimg', 'affine_func_2_anat', 'affine_anat_2_atlas', 'warp_anat_2_atlas', 'reference' ], output_names=['mask_file'], function=create_dfnd_mask), name='create_dfnd_mask') self.create_dfnd_mask.n_procs = settings['num_threads'] # apply nonlinear transform self.applytransforms = MapNode(Function(input_names=[ 'in_file', 'reference4D', 'combined_transforms4D', 'warp_func_2_refimg', 'dfnd_mask' ], output_names=['out_file'], function=applytransforms), iterfield=[ 'in_file', 'reference4D', 'combined_transforms4D', 'warp_func_2_refimg' ], name='applytransforms') self.applytransforms.n_procs = settings['num_threads']