def segment_t1w(t1w, basename, opts=""): """Uses FSLs FAST to segment an anatomical image into GM, WM, and CSF probability maps. Parameters ---------- t1w : str the path to the t1w image to be segmented basename : str the basename for outputs. Often it will be most convenient for this to be the dataset, followed by the subject, followed by the step of processing. Note that this anticipates a path as well; ie, /path/to/dataset_sub_nuis, with no extension. opts : str, optional additional options that can optionally be passed to fast. Desirable options might be -P, which will use prior probability maps if the input T1w MRI is in standard space, by default "" Returns ------- dict dictionary of output files """ # run FAST, with options -t for the image type and -n to # segment into CSF (pve_0), WM (pve_1), GM (pve_2) cmd = f"fast -t 1 {opts} -n 3 -o {basename} {t1w}" gen_utils.run(cmd) out = {} # the outputs out["wm_prob"] = f"{basename}_pve_2.nii.gz" out["gm_prob"] = f"{basename}_pve_1.nii.gz" out["csf_prob"] = f"{basename}_pve_0.nii.gz" return out
def t1w_skullstrip(t1w, out, skull=None): """Skull-strips the t1w image using AFNIs 3dSkullStrip algorithm, which is a modification of FSLs BET specialized to t1w images. Offers robust skull-stripping with no hyperparameters Note: renormalizes the intensities, call extract_t1w_brain instead if you want the original intensity values Parameters ---------- t1w : str path for the input t1w image file out : str path for the output skull-stripped image file skull : str, optional skullstrip parameter pre-set. Default is "none". """ if skull == "below": cmd = f"3dSkullStrip -prefix {out} -input {t1w} -shrink_fac_bot_lim 0.6 -ld 45" elif skull == "cerebelum": cmd = f"3dSkullStrip -prefix {out} -input {t1w} -shrink_fac_bot_lim 0.3 -ld 45" elif skull == "eye": cmd = f"3dSkullStrip -prefix {out} -input {t1w} -no_avoid_eyes -ld 45" elif skull == "general": cmd = f"3dSkullStrip -prefix {out} -input {t1w} -push_to_edge -ld 45" else: cmd = f"3dSkullStrip -prefix {out} -input {t1w} -ld 30" gen_utils.run(cmd)
def get_atlas(atlas_dir, vox_size): """Given the desired location of atlases and the type of processing, ensure we have all the atlases and parcellations. Parameters ---------- atlas_dir : str Path to directory containing atlases. vox_size : str t1w input image voxel dimensions, either 2mm or 1mm Returns ------- tuple filepaths corresponding to the human parcellations, the atlas, and the atlas's mask. atals_brain and lv_mask is None if not fmri. Raises ------ ValueError raised if dimensionality is wrong. NotImplementedError currently raised in lieu of atlas pulling capabilities. """ if vox_size == "2mm": dims = "2x2x2" elif vox_size == "1mm": dims = "1x1x1" else: raise ValueError( "Voxel dimensions of input t1w image not currently supported by m2g." ) # grab atlases if they don't exist if not os.path.exists(atlas_dir): # TODO : re-implement this pythonically with shutil and requests in python3. print("atlas directory not found. Cloning ...") clone = "https://github.com/neurodata/neuroparc.git" gen_utils.run(f"git lfs clone {clone} {atlas_dir}") atlas = os.path.join( atlas_dir, "atlases/reference_brains/MNI152NLin6_res-" + dims + "_T1w.nii.gz") atlas_mask = os.path.join( atlas_dir, "atlases/mask/MNI152NLin6_res-" + dims + "_T1w_descr-brainmask.nii.gz", ) parcellations = [ i for i in glob.glob(atlas_dir + "/atlases/label/Human/*.nii.gz") if dims in i ] parcellations = [ os.path.join(atlas_dir, "label/Human/", l) for l in parcellations ] assert all(map(os.path.exists, parcellations)), "Some parcellations do not exist." assert all( map(os.path.exists, [atlas, atlas_mask]) ), "atlas or atlas_mask, does not exist. You may not have git-lfs -- if not, install it." return parcellations, atlas, atlas_mask
def make_script(input_dir, output_dir, subject, session, data_config, pipeline_config, mem_gb, n_cpus): cpac_script = '/root/.m2g/cpac_script.sh' with open(cpac_script, 'w+', encoding='utf8') as script: script.write(f'''#! /bin/bash . /venv/bin/activate python /code/run.py --data_config_file {data_config} --pipeline_file {pipeline_config} --n_cpus {n_cpus} --mem_gb {mem_gb} {input_dir} {output_dir} participant ''') run(f'chmod +x {cpac_script}') return cpac_script
def gen_tissue(self): """Extracts the brain from the raw t1w image (as indicated by self.t1w), uses it to create WM, GM, and CSF masks, reslices all 4 files to the target voxel resolution and extracts the white matter edge. Each mask is saved to location indicated by self.map_path """ # BET needed for this, as afni 3dautomask only works on 4d volumes print("Extracting brain from raw T1w image...") reg_utils.t1w_skullstrip(self.t1w, self.t1w_brain, self.skull) # QA part of skull strip skullstrip_qa = Path(self.qa) / "skull_strip" if not os.path.exists(skullstrip_qa): skullstrip_qa.mkdir(parents=True, exist_ok=True) print('QA_skullstrip_path ', skullstrip_qa) gen_overlay_pngs(self.t1w_brain, self.t1w, skullstrip_qa) # Segment the t1w brain into probability maps self.maps = reg_utils.segment_t1w(self.t1w_brain, self.map_path) self.wm_mask = self.maps["wm_prob"] self.gm_mask = self.maps["gm_prob"] self.csf_mask = self.maps["csf_prob"] # Generates quality analysis pictures of white matter, gray matter and cerebrospinal fluid qa_fast_png(self.csf_mask, self.gm_mask, self.wm_mask, str(Path(self.qa_reg) / "qa_fast.png")) self.t1w_brain = gen_utils.match_target_vox_res(self.t1w_brain, self.vox_size, self.outdir, sens="anat") self.wm_mask = gen_utils.match_target_vox_res(self.wm_mask, self.vox_size, self.outdir, sens="anat") self.gm_mask = gen_utils.match_target_vox_res(self.gm_mask, self.vox_size, self.outdir, sens="anat") self.csf_mask = gen_utils.match_target_vox_res(self.csf_mask, self.vox_size, self.outdir, sens="anat") # Threshold WM to binary in dwi space self.t_img = load_img(self.wm_mask) self.mask = math_img("img > 0.2", img=self.t_img) self.mask.to_filename(self.wm_mask_thr) # Extract wm edge # TODO : this should be a function in reg_utils so that we can print it to the log cmd = f"fslmaths {self.wm_mask_thr} -edge -bin -mas {self.wm_mask_thr} {self.wm_edge}" print("Extracting white matter edge...") gen_utils.run(cmd)
def combine_xfms(xfm1, xfm2, xfmout): """A function to combine two transformations and output the resulting transformation Parameters ---------- xfm1 : str path to the first transformation xfm2 : str path to the second transformation xfmout : str path for the ouput transformation """ cmd = f"convert_xfm -omat {xfmout} -concat {xfm1} {xfm2}" gen_utils.run(cmd)
def normalize_t1w(inp, out): """ A function that normalizes intensity values for anatomical T1w images. Makes brain extraction much more robust in the event that we have poor shading in our T1w image. **Positional Arguments:** - inp: - the input T1w image. - out: - the output intensity-normalized image. """ cmd = f"3dUnifize -prefix {out} -input {inp}" gen_utils.run(cmd)
def apply_mask(inp, mask, out): """A function to generate a brain-only mask for an input image using 3dcalc Parameters ---------- inp : str path for the input image. If 4d, the mask should be 4d. If 3d, the mask should be 3d. mask : str path to the mask to apply to the data. Should be nonzero in mask region. out : str the path for the output skull-extracted image. """ cmd = f'3dcalc -a {inp} -b {mask} -expr "a*step(b)" -prefix {out}' gen_utils.run(cmd)
def eddy_correct(dwi, corrected_dwi, idx): """Performs eddy-correction (or self-alignment) of a stack of 3D images Parameters ---------- dwi : str Path for the DTI image to be eddy-corrected corrected_dwi : str Path for the corrected and aligned DTI volume in a nifti file idx : str Index of the first B0 volume in the stack """ cmd = f"eddy_correct {dwi} {corrected_dwi} {str(idx)}" gen_utils.run(cmd)
def inverse_warp(ref, out, warp): """Takes a non-linear mapping and finds the inverse. Takes the file conaining warp-coefficients/fields specified in the variable warp (t1w -> mni) and creates its inverse (mni -> t1w) which is saved in the location determined by the variable out Parameters ---------- ref : str path to a file in target space, which is a different target space than warp (a image that has not been mapped to mni) out : str path to the output file, containing warps that are now inverted warp : str path to the warp/shiftmap transform volume wanting to be inverted """ cmd = "invwarp --warp=" + warp + " --out=" + out + " --ref=" + ref gen_utils.run(cmd)
def resample_fsl(base, res, goal_res, interp="spline"): """ A function to resample a base image in fsl to that of a template. **Positional Arguments:** base: - the path to the base image to resample. res: - the filename after resampling. goal_res: - the desired resolution. interp: - the interpolation strategy to use. """ # resample using an isometric transform in fsl cmd = f"flirt -in {base} -ref {base} -out {res} -applyisoxfm {goal_res} -interp {interp}" gen_utils.run(cmd)
def apply_warp(ref, inp, out, warp, xfm=None, mask=None, interp=None, sup=False): """Applies a warp from the structural to reference space in a single step using information about the structural -> ref mapping as well as the functional to structural mapping. Parameters ---------- ref : str path of the reference image to be aligned to inp : str path of the input image to be aligned out : str path for the resulting warped output image warp : str path for the warp coefficent file to go from inp -> ref xfm : str, optional path of the affine transformation matrix file from inp -> ref, by default None mask : str, optional path of filename for mask image (in reference space), by default None interp : str, optional interpolation method {nn, trilinear, sinc, spline}, by default None sup : bool, optional whether to perform automatic intermediary supersampling, by default False """ cmd = ("applywarp --ref=" + ref + " --in=" + inp + " --out=" + out + " --warp=" + warp) if xfm is not None: cmd += " --premat=" + xfm if mask is not None: cmd += " --mask=" + mask if interp is not None: cmd += " --interp=" + interp if sup is True: cmd += " --super --superlevel=a" gen_utils.run(cmd)
def applyxfm(ref, inp, xfm, aligned, interp="trilinear", dof=6): """Aligns two images with a given transform using FSLs flirt command Parameters ---------- ref : str path of reference image to be aligned to as a nifti image file inp : str path of input image to be aligned as a nifti image file xfm : str path to the transform matrix between the two images aligned : str path for the output aligned image interp : str, optional interpolation method, by default "trilinear" dof : int, optional degrees of freedom for the alignment, by default 6 """ cmd = f"flirt -in {inp} -ref {ref} -out {aligned} -init {xfm} -interp {interp} -dof {dof} -applyxfm" gen_utils.run(cmd)
def align_nonlinear(inp, ref, xfm, out, warp, ref_mask=None, in_mask=None, config=None): """Aligns two images using nonlinear methods and stores the transform between them using fnirt Parameters ---------- inp : str path to the input image ref : str path to the reference image that the input will be aligned to xfm : str path to the file containing the affine transform matrix created by reg_utils.align() out : str path for the desired output image warp : str the path to store the output file containing the nonlinear warp coefficients/fields ref_mask : str, optional path to the reference image brain_mask, by default None in_mask : str, optional path for the file with mask in input image space, by default None config : str, optional path to the config file specifying command line arguments, by default None """ cmd = f"fnirt --in={inp} --ref={ref} --aff={xfm} --iout={out} --cout={warp} --warpres=8,8,8" if ref_mask is not None: cmd += f" --refmask={ref_mask} --applyrefmask=1" if in_mask is not None: cmd += f" --inmask={in_mask} --applyinmask=1" if config is not None: cmd += f" --config={config}" gen_utils.run(cmd)
def align_epi(epi, t1, brain, out): """ Algins EPI images to T1w image """ cmd = f"epi_reg --epi={epi} --t1={t1} --t1brain={brain} --out={out}" gen_utils.run(cmd)
def tissue2dwi_align(self): """alignment of ventricle and CC ROI's from MNI space --> dwi and CC and CSF from T1w space --> dwi A function to generate and perform dwi space alignment of avoidance/waypoint masks for tractography. First creates ventricle and CC ROI. Then creates transforms from stock MNI template to dwi space. NOTE: for this to work, must first have called both t1w2dwi_align and atlas2t1w2dwi_align. Raises ------ ValueError Raised if FSL atlas for ventricle reference not found """ # Create MNI-space ventricle mask print("Creating MNI-space ventricle ROI...") if not os.path.isfile(self.mni_atlas): raise ValueError("FSL atlas for ventricle reference not found!") cmd = f"fslmaths {self.mni_vent_loc} -thr 0.1 -bin {self.mni_vent_loc}" gen_utils.run(cmd) cmd = f"fslmaths {self.corpuscallosum} -bin {self.corpuscallosum}" gen_utils.run(cmd) cmd = f"fslmaths {self.corpuscallosum} -sub {self.mni_vent_loc} -bin {self.corpuscallosum}" gen_utils.run(cmd) # Create a transform from the atlas onto T1w. This will be used to transform the ventricles to dwi space. reg_utils.align( self.mni_atlas, self.input_mni, xfm=self.xfm_roi2mni_init, init=None, bins=None, dof=6, cost="mutualinfo", searchrad=True, interp="spline", out=None, ) # Create transform to align roi to mni and T1w using flirt reg_utils.applyxfm(self.input_mni, self.mni_vent_loc, self.xfm_roi2mni_init, self.vent_mask_mni) if self.simple is False: # Apply warp resulting from the inverse MNI->T1w created earlier reg_utils.apply_warp( self.t1w_brain, self.vent_mask_mni, self.vent_mask_t1w, warp=self.mni2t1w_warp, interp="nn", sup=True, ) # Apply warp resulting from the inverse MNI->T1w created earlier reg_utils.apply_warp( self.t1w_brain, self.corpuscallosum, self.corpuscallosum_mask_t1w, warp=self.mni2t1w_warp, interp="nn", sup=True, ) # Applyxfm tissue maps to dwi space reg_utils.applyxfm( self.nodif_B0, self.vent_mask_t1w, self.t1wtissue2dwi_xfm, self.vent_mask_dwi, ) reg_mri_pngs(self.vent_mask_dwi, self.nodif_B0, self.qa_reg) reg_utils.applyxfm( self.nodif_B0, self.corpuscallosum_mask_t1w, self.t1wtissue2dwi_xfm, self.corpuscallosum_dwi, ) reg_mri_pngs(self.corpuscallosum_dwi, self.nodif_B0, self.qa_reg) reg_utils.applyxfm(self.nodif_B0, self.csf_mask, self.t1wtissue2dwi_xfm, self.csf_mask_dwi) reg_mri_pngs(self.csf_mask_dwi, self.nodif_B0, self.qa_reg) reg_utils.applyxfm(self.nodif_B0, self.gm_mask, self.t1wtissue2dwi_xfm, self.gm_in_dwi) reg_mri_pngs(self.gm_in_dwi, self.nodif_B0, self.qa_reg) reg_utils.applyxfm(self.nodif_B0, self.wm_mask, self.t1wtissue2dwi_xfm, self.wm_in_dwi) reg_mri_pngs(self.wm_in_dwi, self.nodif_B0, self.qa_reg) # Threshold WM to binary in dwi space thr_img = nib.load(self.wm_in_dwi) thr_img.get_data()[thr_img.get_data() < 0.15] = 0 nib.save(thr_img, self.wm_in_dwi_bin) # Threshold GM to binary in dwi space thr_img = nib.load(self.gm_in_dwi) thr_img.get_data()[thr_img.get_data() < 0.15] = 0 nib.save(thr_img, self.gm_in_dwi_bin) # Threshold CSF to binary in dwi space thr_img = nib.load(self.csf_mask_dwi) thr_img.get_data()[thr_img.get_data() < 0.99] = 0 nib.save(thr_img, self.csf_mask_dwi) # Threshold WM to binary in dwi space self.t_img = load_img(self.wm_in_dwi_bin) self.mask = math_img("img > 0", img=self.t_img) self.mask.to_filename(self.wm_in_dwi_bin) # Threshold GM to binary in dwi space self.t_img = load_img(self.gm_in_dwi_bin) self.mask = math_img("img > 0", img=self.t_img) self.mask.to_filename(self.gm_in_dwi_bin) # Threshold CSF to binary in dwi space self.t_img = load_img(self.csf_mask_dwi) self.mask = math_img("img > 0", img=self.t_img) self.mask.to_filename(self.csf_mask_dwi_bin) # Create ventricular CSF mask print("Creating ventricular CSF mask...") cmd = f"fslmaths {self.vent_mask_dwi} -kernel sphere 10 -ero -bin {self.vent_mask_dwi}" gen_utils.run(cmd) print("Creating Corpus Callosum mask...") cmd = f"fslmaths {self.corpuscallosum_dwi} -mas {self.wm_in_dwi_bin} -bin {self.corpuscallosum_dwi}" gen_utils.run(cmd) cmd = f"fslmaths {self.csf_mask_dwi} -add {self.vent_mask_dwi} -bin {self.vent_csf_in_dwi}" gen_utils.run(cmd) # Create gm-wm interface image cmd = ( f"fslmaths {self.gm_in_dwi_bin} -mul {self.wm_in_dwi_bin} -add {self.corpuscallosum_dwi} " f"-sub {self.vent_csf_in_dwi} -mas {self.nodif_B0_mask} -bin {self.wm_gm_int_in_dwi}" ) gen_utils.run(cmd)
def align( inp, ref, xfm=None, out=None, dof=12, searchrad=True, bins=256, interp=None, cost="mutualinfo", sch=None, wmseg=None, init=None, finesearch=None, ): """Aligns two images using FSLs flirt function and stores the transform between them Parameters ---------- inp : str path to input image being altered to align with the reference image as a nifti image file ref : str path to reference image being aligned to as a nifti image file xfm : str, optional where to save the 4x4 affine matrix containing the transform between two images, by default None out : str, optional determines whether the image will be automatically aligned and where the resulting image will be saved, by default None dof : int, optional the number of degrees of free dome of the alignment, by default 12 searchrad : bool, optional whether to use the predefined searchradius parameter (180 degree sweep in x, y, and z), by default True bins : int, optional number of histogram bins, by default 256 interp : str, optional interpolation method to be used (trilinear,nearestneighbour,sinc,spline), by default None cost : str, optional cost function to be used in alignment (mutualinfo, corratio, normcorr, normmi, leastsq, labeldiff, or bbr), by default "mutualinfo" sch : str, optional the optional FLIRT schedule, by default None wmseg : str, optional an optional white-matter segmentation for bbr, by default None init : str, optional an initial guess of an alignment in the form of the path to a matrix file, by default None finesearch : int, optional angle in degrees, by default None """ cmd = f"flirt -in {inp} -ref {ref}" if xfm is not None: cmd += f" -omat {xfm}" if out is not None: cmd += f" -out {out}" if dof is not None: cmd += f" -dof {dof}" if bins is not None: cmd += f" -bins {bins}" if interp is not None: cmd += f" -interp {interp}" if cost is not None: cmd += f" -cost {cost}" if searchrad is not None: cmd += " -searchrx -180 180 -searchry -180 180 " + "-searchrz -180 180" if sch is not None: cmd += f" -schedule {sch}" if wmseg is not None: cmd += f" -wmseg {wmseg}" if init is not None: cmd += f" -init {init}" gen_utils.run(cmd)
def t1w2dwi_align(self): """Alignment from t1w to mni, making t1w_mni, and t1w_mni to dwi. A function to perform self alignment. Uses a local optimisation cost function to get the two images close, and then uses bbr to obtain a good alignment of brain boundaries. Assumes input dwi is already preprocessed and brain extracted. """ # Create linear transform/ initializer T1w-->MNI reg_utils.align( self.t1w_brain, self.input_mni, xfm=self.t12mni_xfm_init, bins=None, interp="spline", out=None, dof=12, cost="mutualinfo", searchrad=True, ) # Attempt non-linear registration of T1 to MNI template if self.simple is False: try: print("Running non-linear registration: T1w-->MNI ...") # Use FNIRT to nonlinearly align T1 to MNI template reg_utils.align_nonlinear( self.t1w_brain, self.input_mni, xfm=self.t12mni_xfm_init, out=self.t1_aligned_mni, warp=self.warp_t1w2mni, ref_mask=self.input_mni_mask, config=self.input_mni_sched, ) # Get warp from MNI -> T1 reg_utils.inverse_warp(self.t1w_brain, self.mni2t1w_warp, self.warp_t1w2mni) # Get mat from MNI -> T1 cmd = f"convert_xfm -omat {self.mni2t1_xfm_init} -inverse {self.t12mni_xfm_init}" print(cmd) gen_utils.run(cmd) except RuntimeError("Error: FNIRT failed!"): pass else: # Falling back to linear registration reg_utils.align( self.t1w_brain, self.input_mni, xfm=self.t12mni_xfm, init=self.t12mni_xfm_init, bins=None, dof=12, cost="mutualinfo", searchrad=True, interp="spline", out=self.t1_aligned_mni, sch=None, ) reg_mri_pngs(self.t1_aligned_mni, self.input_mni, self.qa_reg) # Align T1w-->DWI reg_utils.align( self.nodif_B0, self.t1w_brain, xfm=self.t1w2dwi_xfm, bins=None, interp="spline", dof=6, cost="mutualinfo", out=None, searchrad=True, sch=None, ) cmd = f"convert_xfm -omat {self.dwi2t1w_xfm} -inverse {self.t1w2dwi_xfm}" print(cmd) gen_utils.run(cmd) if self.simple is False: # Flirt bbr try: print("Running FLIRT BBR registration: T1w-->DWI ...") reg_utils.align( self.nodif_B0, self.t1w_brain, wmseg=self.wm_edge, xfm=self.dwi2t1w_bbr_xfm, init=self.dwi2t1w_xfm, bins=256, dof=7, searchrad=True, interp="spline", out=None, cost="bbr", finesearch=5, sch="${FSLDIR}/etc/flirtsch/bbr.sch", ) cmd = f"convert_xfm -omat {self.t1w2dwi_bbr_xfm} -inverse {self.dwi2t1w_bbr_xfm}" gen_utils.run(cmd) # Apply the alignment reg_utils.align( self.t1w_brain, self.nodif_B0, init=self.t1w2dwi_bbr_xfm, xfm=self.t1wtissue2dwi_xfm, bins=None, interp="spline", dof=7, cost="mutualinfo", out=self.t1w2dwi, searchrad=True, sch=None, ) except RuntimeError("Error: FLIRT BBR failed!"): pass else: # Apply the alignment reg_utils.align( self.t1w_brain, self.nodif_B0, init=self.t1w2dwi_xfm, xfm=self.t1wtissue2dwi_xfm, bins=None, interp="spline", dof=6, cost="mutualinfo", out=self.t1w2dwi, searchrad=True, sch=None, ) reg_mri_pngs(self.t1w2dwi, self.nodif_B0, self.qa_reg)