def brain(image): ''' Brain Extraction with FSL Params: - image: nifti object, scan to brain extract Output: - brain_image: nifti object, extracted brain ''' affine = image.affine header = image.header tmpfile = 'tmpfile.nii.gz' image.to_filename(tmpfile) # FSL calls mask = fslmaths(image).thr('0.000000').uthr( '100.000000').bin().fillh().run() fslmaths(image).mas(mask).run(tmpfile) bet(tmpfile, tmpfile, fracintensity=0.01) mask = fslmaths(tmpfile).bin().fillh().run() image = fslmaths(image).mas(mask).run() image = nib.Nifti1Image(image.get_data(), affine, header) os.remove(tmpfile) return image
def bias_estimation_calib(calib_name): """ Estimate the bias field from a calibration image using FAST. Parameters ---------- calib_name: pathlib.Path Path to the calibration image from which to estimate the bias field. Returns ------- bias_field: Nifti1Image """ # perform brain extraction using BET betted_m0 = bet(str(calib_name), LOAD, g=0.2, f=0.2, m=True) # run FAST on BET-ed calibration image fast_results = fast(betted_m0["output"], out=LOAD, type=3, b=True, nopve=True) # extract and return bias field bias_field = fast_results["out_bias"] return bias_field
def generate_fmaps(pa_ap_sefms, params, config, distcorr_dir): """ Generate fieldmaps via topup for use with asl_reg. Args: asl_vol0: path to image of stacked blipped images (ie, PEdir as vol0, (oPEdir as vol1), in this case stacked as pa then ap) params: path to text file for topup --datain, PE directions/times config: path to text file for topup --config, other args distcorr_dir: directory in which to put output Returns: n/a, files 'fmap, fmapmag, fmapmagbrain.nii.gz' will be created in output dir """ pwd = os.getcwd() os.chdir(distcorr_dir) # Run topup to get fmap in Hz topup_fmap = op.join(distcorr_dir, 'topup_fmap_hz.nii.gz') cmd = (("topup --imain={} --datain={}".format(pa_ap_sefms, params) + " --config={} --out=topup".format(config)) + " --fout={} --iout={}".format( topup_fmap, op.join(distcorr_dir, 'corrected_sefms.nii.gz'))) sp.run(cmd, shell=True) fmap, fmapmag, fmapmagbrain = [ op.join(distcorr_dir, '{}.nii.gz'.format(s)) for s in ['fmap', 'fmapmag', 'fmapmagbrain'] ] # Convert fmap from Hz to rad/s fmap_spc = rt.ImageSpace(topup_fmap) fmap_arr_hz = nb.load(topup_fmap).get_data() fmap_arr = fmap_arr_hz * 2 * np.pi fmap_spc.save_image(fmap_arr, fmap) # Mean across volumes of corrected sefms to get fmapmag fmapmag_arr = nb.load(op.join(distcorr_dir, "corrected_sefms.nii.gz")).get_data() fmapmag_arr = fmapmag_arr.mean(-1) fmap_spc.save_image(fmapmag_arr, fmapmag) # Run BET on fmapmag to get brain only version bet(fmap_spc.make_nifti(fmapmag_arr), output=fmapmagbrain) os.chdir(pwd)
def run(wsp): """ Do initialization on supplied structural data - copy relevant image and do brain extraction FIXME copy across all supplied structural data """ wsp.log.write("\nInitialising structural data\n") wsp.sub("structural") if wsp.fslanat: wsp.log.write( " - Using FSL_ANAT output directory for structural data: %s\n" % wsp.fslanat) biascorr = os.path.join(wsp.fslanat, "T1_biascorr") biascorr_brain = os.path.join(wsp.fslanat, "T1_biascorr_brain") if glob.glob(biascorr + ".*") and glob.glob(biascorr_brain + ".*"): wsp.log.write(" - Using bias-corrected structural images\n") wsp.structural.struc = Image(biascorr) wsp.structural.brain = Image(biascorr_brain) else: wsp.log.write(" - Using non bias-corrected structural images\n") wsp.structural.struc = Image(os.path.join(wsp.fslanat, "T1")) wsp.structural.brain = Image(os.path.join(wsp.fslanat, "T1_brain")) elif wsp.struc: wsp.log.write(" - Using structural image provided by user: %s\n" % wsp.struc.name) wsp.structural.struc = wsp.struc wsp.structural.brain = wsp.struc_brain #elif wsp.structural.struc_lores # wsp.log.write("Low-resolution tructural image: %s\n" % wsp.structural.struc_lores.name) else: wsp.log.write( " - No structural data supplied - output will be native space only\n" ) if wsp.structural.struc is not None and wsp.structural.brain is None: wsp.log.write(" - Brain-extracting structural image\n") bet_result = fsl.bet(wsp.structural.struc, output=fsl.LOAD, seg=True, mask=True, log=wsp.fsllog) wsp.structural.brain = bet_result["output"] #wsp.structural.brain_mask = bet_result["output_mask"] if wsp.structural.brain is not None and wsp.structural.brain_mask is None: # FIXME - for now get the mask by binarising the brain image for compatibility with oxford_asl # although this gives slightly different results compared to using the mask returned by BET wsp.structural.brain_mask = Image( (wsp.structural.brain.data != 0).astype(np.int), header=wsp.structural.struc.header) if wsp.structural.struc is not None: segment(wsp)
def get_mask(wsp): """ Generate the mask to use - if it has not been provided by the user """ if wsp.mask is None: wsp.datamean = Image(np.mean(wsp.dscdata.data, axis=-1), header=wsp.dscdata.header) bet_result = fsl.bet(wsp.datamean, seg=False, mask=True, output=fsl.LOAD, log=wsp.fsllog) wsp.mask = bet_result["output_mask"] wsp.log.write( " - Mask generated by running BET on time-averaged DSC data\n") else: wsp.log.write(" - Mask supplied by user\n")
def generate_fmaps(pa_ap_sefms, params, config, distcorr_dir, gdc_warp, interpolation=3): """ Generate fieldmaps via topup for use with asl_reg. Args: asl_vol0: path to image of stacked blipped images (ie, PEdir as vol0, (oPEdir as vol1), in this case stacked as pa then ap) params: path to text file for topup --datain, PE directions/times config: path to text file for topup --config, other args distcorr_dir: directory in which to put output gdc_warp: path to gradient_unwarp's gradient distortion correction warp interpolation: order of interpolation to be used when applying registrations, default=3 Returns: n/a, files 'fmap, fmapmag, fmapmagbrain.nii.gz' will be created in output dir """ # set up logger logger = logging.getLogger("HCPASL.distortion_estimation") logger.info("Running generate_fmaps()") logger.info(f"Spin Echo Field Maps: {pa_ap_sefms}") logger.info(f"Topup param file: {params}") logger.info(f"Topup config file: {config}") logger.info(f"Topup output directory: {distcorr_dir}") logger.info(f"Gradient distortion correction warp: {gdc_warp}") logger.info(f"Interpolation order: {interpolation}") pwd = os.getcwd() os.chdir(distcorr_dir) # apply gradient distortion correction to stacked SEFMs gdc = rt.NonLinearRegistration.from_fnirt(coefficients=str(gdc_warp), src=str(pa_ap_sefms), ref=str(pa_ap_sefms), intensity_correct=True) gdc_corr_pa_ap_sefms = gdc.apply_to_image(src=str(pa_ap_sefms), ref=str(pa_ap_sefms), order=interpolation, cores=1) gdc_corr_pa_ap_sefms_name = distcorr_dir / "merged_sefms_gdc.nii.gz" nb.save(gdc_corr_pa_ap_sefms, gdc_corr_pa_ap_sefms_name) # Run topup to get fmap in Hz topup_fmap = op.join(distcorr_dir, 'topup_fmap_hz.nii.gz') topup_cmd = [ "topup", f"--imain={gdc_corr_pa_ap_sefms_name}", f"--datain={params}", f"--config={config}", f"--out=topup", f"--iout={op.join(distcorr_dir, 'corrected_sefms.nii.gz')}", f"--fout={topup_fmap}", f"--dfout={op.join(distcorr_dir, 'WarpField')}", f"--rbmout={op.join(distcorr_dir, 'MotionMatrix')}", f"--jacout={op.join(distcorr_dir, 'Jacobian')}", "--verbose" ] logger.info(f"Topup command: {' '.join(topup_cmd)}") process = sp.Popen(topup_cmd, stdout=sp.PIPE) while 1: retcode = process.poll() line = process.stdout.readline().decode("utf-8") logger.info(line) if line == "" and retcode is not None: break if retcode != 0: logger.info(f"retcode={retcode}") logger.exception("Process failed.") fmap, fmapmag, fmapmagbrain = [ op.join(distcorr_dir, '{}.nii.gz'.format(s)) for s in ['fmap', 'fmapmag', 'fmapmagbrain'] ] # Convert fmap from Hz to rad/s logger.info("Converting fieldmap from Hz to rad/s.") fmap_spc = rt.ImageSpace(topup_fmap) fmap_arr_hz = nb.load(topup_fmap).get_data() fmap_arr = fmap_arr_hz * 2 * np.pi fmap_spc.save_image(fmap_arr, fmap) # Apply gdc warp from gradient_unwarp and topup's EPI-DC # warp (just generated) in one interpolation step logger.info( "Applying gdc and epi-dc to fieldmap images in one interpolation step." ) pa_ap_sefms_gdc_dc = apply_gdc_and_topup(pa_ap_sefms, distcorr_dir, gdc_warp, interpolation=interpolation) # Mean across volumes of corrected sefms to get fmapmag logger.info( "Taking mean of corrected fieldmap images to get fmapmag.nii.gz") fmapmag_img = nb.nifti1.Nifti1Image( pa_ap_sefms_gdc_dc.get_fdata().mean(-1), affine=pa_ap_sefms_gdc_dc.affine) nb.save(fmapmag_img, fmapmag) # Run BET on fmapmag to get brain only version logger.info("Running BET on fmapmag for brain-extracted version.") bet(fmapmag, output=fmapmagbrain) os.chdir(pwd)
def correct_M0(subject_dir, mt_factors): """ Correct the M0 images for a particular subject whose data is stored in `subject_dir`. The corrections to be performed include: - Bias-field correction - Magnetisation Transfer correction Inputs - `subject_dir` = pathlib.Path object specifying the subject's base directory - `mt_factors` = pathlib.Path object specifying the location of empirically estimated MT correction scaling factors """ # load json containing info on where files are stored json_dict = load_json(subject_dir) # do for both m0 images for the subject, calib0 and calib1 calib_names = [json_dict['calib0_img'], json_dict['calib1_img']] for calib_name in calib_names: # get calib_dir and other info calib_path = Path(calib_name) calib_dir = calib_path.parent calib_name_stem = calib_path.stem.split('.')[0] # run BET on m0 image betted_m0 = bet(calib_name, LOAD) # create directories to store results fast_dir = calib_dir / 'FAST' biascorr_dir = calib_dir / 'BiasCorr' mtcorr_dir = calib_dir / 'MTCorr' create_dirs([fast_dir, biascorr_dir, mtcorr_dir]) # estimate bias field on brain-extracted m0 image # run FAST, storing results in directory fast_base = fast_dir / calib_name_stem fast( betted_m0['output'], # output of bet out=str(fast_base), type=3, # image type, 3=PD image b=True, # output estimated bias field nopve=True # don't need pv estimates ) bias_name = fast_dir / f'{calib_name_stem}_bias.nii.gz' # apply bias field to original m0 image (i.e. not BETted) biascorr_name = biascorr_dir / f'{calib_name_stem}_restore.nii.gz' fslmaths(calib_name).div(str(bias_name)).run(str(biascorr_name)) # apply mt_factors to bias-corrected m0 image mtcorr_name = mtcorr_dir / f'{calib_name_stem}_mtcorr.nii.gz' fslmaths(str(biascorr_name)).mul(str(mt_factors)).run(str(mtcorr_name)) # add locations of above files to the json important_names = { f'{calib_name_stem}_bias': str(bias_name), f'{calib_name_stem}_bc': str(biascorr_name), f'{calib_name_stem}_mc': str(mtcorr_name) } update_json(important_names, json_dict)
def main(): # argument handling parser = argparse.ArgumentParser() parser.add_argument("study_dir", help="Path of the base study directory.") parser.add_argument("sub_number", help="Subject number.") parser.add_argument( "-g", "--grads", help="Filename of the gradient coefficients for gradient" + "distortion correction (optional).") parser.add_argument( "-t", "--target", help="Which space we want to register to. Can be either 'asl' for " + "registration to the first volume of the ASL series or " + "'structural' for registration to the T1w image. Default " + " is 'asl'.", default="asl") args = parser.parse_args() study_dir = args.study_dir sub_id = args.sub_number grad_coefficients = args.grads target = args.target # For debug, re-use existing intermediate files force_refresh = True # Input, output and intermediate directories # Create if they do not already exist. sub_base = op.abspath(op.join(study_dir, sub_id)) grad_coefficients = op.abspath(grad_coefficients) pvs_dir = op.join(sub_base, "T1w", "ASL", "PVEs") t1_asl_dir = op.join(sub_base, "T1w", "ASL") distcorr_dir = op.join(sub_base, "ASL", "TIs", "SecondPass", "DistCorr") reg_dir = op.join(sub_base, 'T1w', 'ASL', 'reg') t1_dir = op.join(sub_base, "T1w") asl_dir = op.join(sub_base, "ASL", "TIs", "SecondPass", "STCorr2") asl_out_dir = op.join(t1_asl_dir, "TIs", "DistCorr") calib_out_dir = op.join(t1_asl_dir, "Calib", "Calib0", "DistCorr") [ os.makedirs(d, exist_ok=True) for d in [ pvs_dir, t1_asl_dir, distcorr_dir, reg_dir, asl_out_dir, calib_out_dir ] ] # Images required for processing asl = op.join(asl_dir, "tis_stcorr.nii.gz") struct = op.join(t1_dir, "T1w_acpc_dc_restore.nii.gz") struct_brain = op.join(t1_dir, "T1w_acpc_dc_restore_brain.nii.gz") struct_brain_mask = op.join(t1_dir, "T1w_acpc_dc_restore_brain_mask.nii.gz") asl_vol0 = op.join(asl_dir, "tis_stcorr_vol1.nii.gz") if (not op.exists(asl_vol0) or force_refresh) and target == 'asl': cmd = "fslroi {} {} 0 1".format(asl, asl_vol0) sp.run(cmd.split(" "), check=True) # Create ASL-gridded version of T1 image t1_asl_grid = op.join(t1_dir, "ASL", "reg", "ASL_grid_T1w_acpc_dc_restore.nii.gz") if (not op.exists(t1_asl_grid) or force_refresh) and target == 'asl': asl_spc = rt.ImageSpace(asl) t1_spc = rt.ImageSpace(struct) t1_asl_grid_spc = t1_spc.resize_voxels(asl_spc.vox_size / t1_spc.vox_size) nb.save( rt.Registration.identity().apply_to_image(struct, t1_asl_grid_spc), t1_asl_grid) # Create ASL-gridded version of T1 image t1_asl_grid_mask = op.join( reg_dir, "ASL_grid_T1w_acpc_dc_restore_brain_mask.nii.gz") if (not op.exists(t1_asl_grid_mask) or force_refresh) and target == 'asl': asl_spc = rt.ImageSpace(asl) t1_spc = rt.ImageSpace(struct_brain) t1_asl_grid_spc = t1_spc.resize_voxels(asl_spc.vox_size / t1_spc.vox_size) t1_mask = binarise_image(struct_brain) t1_mask_asl_grid = rt.Registration.identity().apply_to_array( t1_mask, t1_spc, t1_asl_grid_spc) # Re-binarise downsampled mask and save t1_asl_grid_mask_array = binary_fill_holes( t1_mask_asl_grid > 0.25).astype(np.float32) t1_asl_grid_spc.save_image(t1_asl_grid_mask_array, t1_asl_grid_mask) # MCFLIRT ASL using the calibration as reference calib = op.join(sub_base, 'ASL', 'Calib', 'Calib0', 'MTCorr', 'calib0_mtcorr.nii.gz') asl = op.join(sub_base, 'ASL', 'TIs', 'tis.nii.gz') mcdir = op.join(sub_base, 'ASL', 'TIs', 'SecondPass', 'MoCo', 'asln2m0.mat') asl2calib_mc = rt.MotionCorrection.from_mcflirt(mcdir, asl, calib) # Rebase the motion correction to target volume 0 of ASL # The first registration in the series gives us ASL-calibration transform calib2asl0 = asl2calib_mc[0].inverse() asl_mc = rt.chain(asl2calib_mc, calib2asl0) # Generate the gradient distortion correction warp gdc_path = op.join(distcorr_dir, 'fullWarp_abs.nii.gz') if (not op.exists(gdc_path) or force_refresh) and target == 'asl': generate_gdc_warp(asl_vol0, grad_coefficients, distcorr_dir) gdc = rt.NonLinearRegistration.from_fnirt(gdc_path, asl_vol0, asl_vol0, intensity_correct=True, constrain_jac=(0.01, 100)) # Stack the cblipped images together for use with topup pa_sefm, ap_sefm = find_field_maps(study_dir, sub_id) pa_ap_sefms = op.join(distcorr_dir, 'merged_sefms.nii.gz') if (not op.exists(pa_ap_sefms) or force_refresh) and target == 'asl': rt.ImageSpace.save_like( pa_sefm, np.stack( (nb.load(pa_sefm).get_data(), nb.load(ap_sefm).get_data()), axis=-1), pa_ap_sefms) topup_params = op.join(distcorr_dir, 'topup_params.txt') generate_topup_params(topup_params) topup_config = "b02b0.cnf" # Note this file doesn't exist in scope, # but topup knows where to find it # Generate fieldmaps for use with asl_reg (via topup) fmap, fmapmag, fmapmagbrain = [ op.join(distcorr_dir, '{}.nii.gz'.format(s)) for s in ['fmap', 'fmapmag', 'fmapmagbrain'] ] if ((not all([op.exists(p) for p in [fmap, fmapmag, fmapmagbrain]])) or force_refresh) and target == 'asl': generate_fmaps(pa_ap_sefms, topup_params, topup_config, distcorr_dir) # get linear registration from asl to structural if target == 'asl': unreg_img = asl_vol0 elif target == 'structural': # register perfusion-weighted image to structural instead of asl 0 unreg_img = op.join(sub_base, "ASL", "TIs", "SecondPass", "OxfordASL", "native_space", "perfusion.nii.gz") # apply gdc to unreg_img before getting registration to structural # only apply to asl_vol0 as perfusion image has already had gdc applied distcorr_out_dir = asl_out_dir if target == 'structural' else distcorr_dir gdc_tis_vol1_name = op.join(distcorr_out_dir, "gdc_tis_vol1.nii.gz") if (not op.exists(gdc_tis_vol1_name) or force_refresh) and target == 'asl': gdc_tis_vol1 = gdc.apply_to_image(src=unreg_img, ref=unreg_img) unreg_img = gdc_tis_vol1_name nb.save(gdc_tis_vol1, unreg_img) # Initial (linear) asl to structural registration, via first round of asl_reg asl2struct_initial_path = op.join( reg_dir, 'asl2struct_init.mat' if target == 'asl' else 'asl2struct_final.mat') if not op.exists(asl2struct_initial_path) or force_refresh: generate_asl2struct_initial(unreg_img, reg_dir, struct, struct_brain) asl2struct_initial_path_temp = op.join(reg_dir, 'asl2struct.mat') os.replace(asl2struct_initial_path_temp, asl2struct_initial_path) asl2struct_initial = rt.Registration.from_flirt(asl2struct_initial_path, src=unreg_img, ref=struct) # Get brain mask in asl space if target == 'asl': mask_name = op.join(reg_dir, "asl_vol1_mask_init.nii.gz") else: mask_name = op.join(reg_dir, "asl_vol1_mask_final.nii.gz") if not op.exists(mask_name) or force_refresh: asl_mask = generate_asl_mask(struct_brain, unreg_img, asl2struct_initial) rt.ImageSpace.save_like(unreg_img, asl_mask, mask_name) # Brain extract volume 0 of asl series gdc_unreg_img_brain = op.join(sub_base, "ASL", "TIs", "SecondPass", "DistCorr", "gdc_tis_vol1_brain.nii.gz") if (not op.exists(gdc_unreg_img_brain) or force_refresh) and target == 'asl': bet(unreg_img, gdc_unreg_img_brain) unreg_img = gdc_unreg_img_brain # Generate a binary WM mask in the space of the T1 (using FS' aparc+aseg) wmmask = op.join(sub_base, "T1w", "wmmask.nii.gz") if (not op.exists(wmmask) or force_refresh) and target == 'asl': aparc_seg = op.join(t1_dir, "aparc+aseg.nii.gz") wmmask_img = generate_wmmask(aparc_seg) rt.ImageSpace.save_like(struct, wmmask_img, wmmask) # Generate the EPI distortion correction warp via asl_reg --final epi_dc_path = op.join( distcorr_dir, 'asl2struct_warp_init.nii.gz' if target == 'asl' else 'asl2struct_warp_final.nii.gz') if not op.exists(epi_dc_path) or force_refresh: epi_dc_path_temp = op.join(distcorr_dir, 'asl2struct_warp.nii.gz') generate_epidc_warp(unreg_img, struct, struct_brain, mask_name, wmmask, asl2struct_initial, fmap, fmapmag, fmapmagbrain, distcorr_dir) # rename warp so it isn't overwritten os.replace(epi_dc_path_temp, epi_dc_path) epi_dc = rt.NonLinearRegistration.from_fnirt(epi_dc_path, mask_name, struct, intensity_correct=True, constrain_jac=(0.01, 100)) # if ending in asl space, chain struct2asl transformation if target == 'asl': struct2asl_aslreg = op.join(distcorr_out_dir, "struct2asl.mat") struct2asl_aslreg = rt.Registration.from_flirt(struct2asl_aslreg, src=struct, ref=asl) epi_dc = rt.chain(epi_dc, struct2asl_aslreg) # Final ASL transforms: moco, grad dc, # epi dc (incorporating asl->struct reg) asl = op.join(asl_dir, "tis_stcorr.nii.gz") reference = t1_asl_grid if target == 'structural' else asl asl_outpath = op.join(distcorr_out_dir, "tis_distcorr.nii.gz") if not op.exists(asl_outpath) or force_refresh: asl2struct_mc_dc = rt.chain(asl_mc, gdc, epi_dc) asl_corrected = asl2struct_mc_dc.apply_to_image(src=asl, ref=reference, cores=mp.cpu_count()) nb.save(asl_corrected, asl_outpath) # Final calibration transforms: calib->asl, grad dc, # epi dc (incorporating asl->struct reg) calib_outpath = op.join(calib_out_dir, "calib0_dcorr.nii.gz") if (not op.exists(calib_outpath) or force_refresh) and target == 'structural': calib2struct_dc = rt.chain(calib2asl0, gdc, epi_dc) calib_corrected = calib2struct_dc.apply_to_image(src=calib, ref=reference) nb.save(calib_corrected, calib_outpath) # Final scaling factors transforms: moco, grad dc, # epi dc (incorporating asl->struct reg) sfs_name = op.join(asl_dir, "combined_scaling_factors.nii.gz") sfs_outpath = op.join(distcorr_out_dir, "combined_scaling_factors.nii.gz") if not op.exists(sfs_outpath) or force_refresh: # don't chain transformations together if we don't have to try: asl2struct_mc_dc except NameError: asl2struct_mc_dc = rt.chain(asl_mc, gdc, epi_dc) sfs_corrected = asl2struct_mc_dc.apply_to_image(src=sfs_name, ref=reference, cores=mp.cpu_count()) nb.save(sfs_corrected, sfs_outpath) # create ti image in asl space slicedt = 0.059 tis = [1.7, 2.2, 2.7, 3.2, 3.7] sliceband = 10 ti_asl = op.join(sub_base, "ASL", "TIs", "timing_img.nii.gz") if (not op.exists(ti_asl) or force_refresh) and target == 'asl': create_ti_image(asl, tis, sliceband, slicedt, ti_asl) # transform ti image into t1 space ti_t1 = op.join(t1_asl_dir, "timing_img.nii.gz") if (not op.exists(ti_t1) or force_refresh) and target == 'structural': asl2struct = op.join(distcorr_dir, "asl2struct.mat") asl2struct = rt.Registration.from_flirt(asl2struct, src=asl, ref=struct) ti_t1_img = asl2struct.apply_to_image(src=ti_asl, ref=reference) nb.save(ti_t1_img, ti_t1)
def setup_mtestimation(subject_dir, rois=[ 'wm', ]): """ Perform the initial processing needed for estimation of the MT Effect. This includes: - Creating sub-directories for storing the results - Finding T1 and mbPCASL directories and scans - Split mbPCASL sequence into its constituent components - Run fsl_anat - Create a json to keep track of important files and directories """ # get subject name subject_name = subject_dir.parts[-1] print(subject_name) # create results directories asl_dir = subject_dir / 'ASL' calib0_dir = asl_dir / 'Calib/Calib0' calib1_dir = asl_dir / 'Calib/Calib1' create_dirs([asl_dir, calib0_dir, calib1_dir]) # obtain calibration images from ASL sequence mbpcasl_dir = list( (subject_dir / f'{subject_name}_V1_B/scans').glob('**/*mbPCASLhr'))[0] mbpcasl = mbpcasl_dir / f'resources/NIFTI/files/{subject_name}_V1_B_mbPCASLhr_PA.nii.gz' calib0_name = calib0_dir / 'calib0.nii.gz' calib1_name = calib1_dir / 'calib1.nii.gz' fslroi(str(mbpcasl), calib0_name, 88, 1) fslroi(str(mbpcasl), calib1_name, 89, 1) # initialise dict json_name = asl_dir / 'ASL.json' important_dict = { "calib_dir": str(calib0_dir.parent), "calib0_dir": str(calib0_dir), "calib1_dir": str(calib1_dir), "calib0_img": str(calib0_name), "calib1_img": str(calib1_dir), "json_name": str(json_name) } # structural directory t1_dir = list( (subject_dir / f'{subject_name}_V1_A/scans').glob('**/*T1w'))[0] struc_dir = t1_dir / 'resources/NIFTI/files' struc_name = list(struc_dir.glob(f'**/{subject_name}_*.nii.gz'))[0] fsl_anat_dir = struc_dir / 'ASL/struc' calib0struct_dir = struc_dir / 'ASL/Calib/Calib0' calib1struct_dir = struc_dir / 'ASL/Calib/Calib1' create_dirs([calib0struct_dir, calib1struct_dir]) # run fsl_anat fsl_anat(str(struc_name), str(fsl_anat_dir), clobber=True, nosubcortseg=True) fsl_anat_dir = fsl_anat_dir.parent / f'{fsl_anat_dir.stem}.anat' t1_name = fsl_anat_dir / 'T1_biascorr.nii.gz' t1_brain_name = fsl_anat_dir / 'T1_biascorr_brain.nii.gz' # get ventricles mask if csf if 'csf' in rois: # initialise atlas list atlases.rescanAtlases() harv_ox_prob_2mm = atlases.loadAtlas('harvardoxford-subcortical', resolution=2.0) vent_img = Image(harv_ox_prob_2mm.data[:, :, :, 2] + harv_ox_prob_2mm.data[:, :, :, 13], header=harv_ox_prob_2mm.header) vent_img = fslmaths(vent_img).thr(0.1).bin().ero().run(LOAD) # we already have registration from T1 to MNI struc2mni_warp = fsl_anat_dir / 'MNI_to_T1_nonlin_field.nii.gz' # apply warp to ventricles image vent_t1_name = fsl_anat_dir / 'ventricles_mask.nii.gz' applywarp(vent_img, str(t1_brain_name), str(vent_t1_name), warp=str(struc2mni_warp)) # re-threshold vent_t1 = fslmaths(str(vent_t1_name)).thr( PVE_THRESHOLDS['csf']).bin().run(LOAD) # mask pve estimate by ventricles mask fslmaths(str(fsl_anat_dir / PVE_NAMES['csf'])).mas(vent_t1).run( str(vent_t1_name)) # bias-field correction for calib_name in (calib0_name, calib1_name): calib_name_stem = calib_name.stem.split('.')[0] # run bet betted_m0 = bet(str(calib_name), LOAD) # create directories to save results fast_dir = calib_name.parent / 'FAST' biascorr_dir = calib_name.parent / 'BiasCorr' create_dirs([fast_dir, biascorr_dir]) # run FAST on brain-extracted m0 image fast_base = fast_dir / calib_name_stem fast(betted_m0['output'], out=str(fast_base), type=3, b=True, nopve=True) bias_name = fast_dir / f'{calib_name_stem}_bias.nii.gz' # apply bias field to original m0 image (i.e. not BETted) biascorr_name = biascorr_dir / f'{calib_name_stem}_restore.nii.gz' fslmaths(str(calib_name)).div(str(bias_name)).run(str(biascorr_name)) # obtain registration from structural to calibration image mask_dir = biascorr_name.parent / 'masks' create_dirs([ mask_dir, ]) cmd = [ 'asl_reg', f'-i {biascorr_name}', f'-s {t1_name}', f'--sbet {t1_brain_name}', f'-o {mask_dir}' ] subprocess.run(" ".join(cmd), shell=True) # apply transformation to the pve map for tissue in rois: roi_dir = mask_dir / tissue create_dirs([ roi_dir, ]) if tissue == 'combined': # check that gm and wm pves already exist gm_pve = mask_dir / 'gm/pve_gm.nii.gz' wm_pve = mask_dir / 'wm/pve_wm.nii.gz' if not (gm_pve.exists() and wm_pve.exists()): raise Exception("WM and GM PVEs don't exist.") gm_mask_name = roi_dir / 'gm_mask.nii.gz' wm_mask_name = roi_dir / 'wm_mask.nii.gz' fslmaths(str(gm_pve)).thr(PVE_THRESHOLDS[tissue]).bin().run( str(gm_mask_name)) fslmaths(str(wm_pve)).thr(PVE_THRESHOLDS[tissue]).bin().run( str(wm_mask_name)) gm_masked_name = roi_dir / f'{calib_name_stem}_gm_masked' wm_masked_name = roi_dir / f'{calib_name_stem}_wm_masked' fslmaths(str(biascorr_name)).mul(str(gm_mask_name)).run( str(gm_masked_name)) fslmaths(str(biascorr_name)).mul(str(wm_mask_name)).run( str(wm_masked_name)) # update dictionary new_dict = { f'{calib_name_stem}_{tissue}_masked': [str(gm_masked_name), str(wm_masked_name)] } else: if tissue == 'csf': pve_struct_name = vent_t1_name else: pve_struct_name = fsl_anat_dir / PVE_NAMES[tissue] pve_asl_name = roi_dir / f'pve_{tissue}.nii.gz' struct2asl_name = mask_dir / 'struct2asl.mat' applyxfm(str(pve_struct_name), str(biascorr_name), str(struct2asl_name), str(pve_asl_name)) # threshold and binarise the ASL-space pve map mask_name = roi_dir / f'{tissue}_mask.nii.gz' fslmaths(str(pve_asl_name)).thr( PVE_THRESHOLDS[tissue]).bin().run(str(mask_name)) # apply the mask to the calibration image masked_name = mask_dir / f'{tissue}_masked.nii.gz' fslmaths(str(biascorr_name)).mul(str(mask_name)).run( str(masked_name)) # update dictionary new_dict = { f'{calib_name_stem}_{tissue}_masked': str(masked_name) } important_dict.update(new_dict) # save json with open(json_name, 'w') as fp: json.dump(important_dict, fp, sort_keys=True, indent=4) return (subject_dir, 0)
def setup_mtestimation(subject_dir, coeffs_path, rois=[ 'wm', ], interpolation=3, ignore_dropouts=False, force_refresh=True): try: # find files and separate calibration images from mbPCASL sequence names_dict = setup(subject_dir) calib0_name, calib1_name = [ names_dict[f"calib{n}_name"] for n in (0, 1) ] calib_stems = [ c.stem.split(".")[0] for c in (calib0_name, calib1_name) ] # create results directory suf = "_ignoredropouts" if ignore_dropouts else "" results_dirs = [ names_dict[f'calib{n}_dir'] / f"SEbased_MT_t1mask{suf}" for n in (0, 1) ] create_dirs(results_dirs) t1_name, t1_brain_name = [ names_dict[name] for name in ("t1_name", "t1_brain_name") ] # generate white matter mask in T1 space for use in BBRegistration wm_mask = names_dict["aslt1_dir"] / "wm_mask.nii.gz" if not wm_mask.exists() or force_refresh: nb.save(generate_tissue_mask(names_dict["aparc_aseg"], "wm"), wm_mask) # setup distortion correction results directories distcorr_dir = names_dict["calib0_dir"].parent / "DistCorr" gdc_dir = distcorr_dir / "gradient_unwarp" topup_dir = distcorr_dir / "topup" calib_distcorr_dirs = [r / "DistCorr" for r in results_dirs] create_dirs((distcorr_dir, gdc_dir, topup_dir, *calib_distcorr_dirs)) # gradient distortion correction gdc_warp = gdc_dir / "fullWarp_abs.nii.gz" if not gdc_warp.exists() or force_refresh: distortion_correction.generate_gdc_warp(names_dict["calib0_name"], coeffs_path, gdc_dir, interpolation) # topup fmap, fmapmag, fmapmagbrain = [ topup_dir / f"fmap{ext}.nii.gz" for ext in ("", "mag", "magbrain") ] if not all([f.exists() for f in (fmap, fmapmag, fmapmagbrain)]) or force_refresh: pa_ap_sefms = topup_dir / "merged_sefms.nii.gz" distortion_correction.stack_fmaps(names_dict["pa_sefm"], names_dict["ap_sefm"], pa_ap_sefms) topup_params = topup_dir / "topup_params.txt" distortion_correction.generate_topup_params(topup_params) topup_config = "b02b0.cnf" distortion_correction.generate_fmaps(pa_ap_sefms, topup_params, topup_config, topup_dir, gdc_warp) # load gdc warp gdc_warp_reg = rt.NonLinearRegistration.from_fnirt( coefficients=str(gdc_warp), src=str(calib0_name), ref=str(calib0_name), intensity_correct=True) # apply gdc and epidc to both calibration images for calib_name, results_dir in zip((calib0_name, calib1_name), calib_distcorr_dirs): # apply gdc to the calibration image calib_name_stem = calib_name.stem.split(".")[0] gdc_calib_name = results_dir / f"{calib_name_stem}_gdc.nii.gz" if not gdc_calib_name.exists() or force_refresh: gdc_calib = gdc_warp_reg.apply_to_image(str(calib_name), str(calib_name), order=interpolation) nb.save(gdc_calib, gdc_calib_name) # estimate initial registration via asl_reg asl_lin_reg = results_dir / "asl_reg_linear" asl_lin_reg.mkdir(exist_ok=True) asl2struct_lin = asl_lin_reg / "asl2struct.mat" if not asl2struct_lin.exists() or force_refresh: linear_asl_reg(gdc_calib_name, asl_lin_reg, t1_name, t1_brain_name, wm_mask) init_linear = rt.Registration.from_flirt(str(asl2struct_lin), src=str(calib_name), ref=str(t1_name)) # run bet - should I instead get mask from T1 here? bet_results = results_dir / "bet" bet_mask = results_dir / "bet_mask.nii.gz" if not bet_mask.exists() or force_refresh: betted_m0 = bet(str(gdc_calib_name), str(bet_results), g=0.2, f=0.2, m=True) # get epi distortion correction warps asl_nonlin_reg = results_dir / "asl_reg_nonlinear" asl_nonlin_reg.mkdir(exist_ok=True) struct2asl, asl2struct_warp = [ asl_nonlin_reg / n for n in ("struct2asl.mat", "asl2struct_warp.nii.gz") ] if not all([f.exists() for f in (asl2struct_warp, struct2asl) ]) or force_refresh: distortion_correction.generate_epidc_warp( str(gdc_calib_name), str(t1_name), t1_brain_name, bet_mask, wm_mask, init_linear, fmap, fmapmag, fmapmagbrain, str(asl_nonlin_reg)) # chain gradient and epi distortion correction warps together asl2struct_warp_reg = rt.NonLinearRegistration.from_fnirt( coefficients=str(asl2struct_warp), src=str(calib_name), ref=str(t1_name), intensity_correct=True) struct2asl_reg = rt.Registration.from_flirt(str(struct2asl), src=str(t1_name), ref=str(calib_name)) dc_warp = rt.chain(gdc_warp_reg, asl2struct_warp_reg, struct2asl_reg) dc_calib_name = results_dir / f"{calib_name_stem}_dc.nii.gz" if not dc_calib_name.exists() or force_refresh: dc_calib = dc_warp.apply_to_image(str(calib_name), str(calib_name), order=interpolation) nb.save(dc_calib, dc_calib_name) # estimate the bias field bias_name = results_dir / f"{calib_name_stem}_bias.nii.gz" sebased_dir = results_dir / "sebased" if not bias_name.exists() or force_refresh: sebased_dir.mkdir(exist_ok=True) bias_field = bias_estimation( dc_calib_name, "sebased", results_dir=sebased_dir, t1_name=t1_name, t1_brain_name=t1_brain_name, aparc_aseg=names_dict["aparc_aseg"], fmapmag=fmapmag, fmapmagbrain=fmapmagbrain, interpolation=interpolation, force_refresh=force_refresh, wmseg_name=wm_mask, struct2asl=struct2asl) nb.save(bias_field, bias_name) else: bias_field = nb.load(bias_name) # bias correct the distortion-corrected calibration image bc_calib_name = results_dir / f"{calib_name_stem}_restore.nii.gz" if not bc_calib_name.exists() or force_refresh: fslmaths(str(dc_calib_name)).div(bias_field).run( str(bc_calib_name)) # create mask directories roi_dirs = [results_dir / "masks" / roi for roi in rois] create_dirs(roi_dirs) # load Dropouts dropouts_inv = nb.load(sebased_dir / "Dropouts_inv.nii.gz") for roi, roi_dir in zip(rois, roi_dirs): # get tissue masks in calibration image space base_mask_call = partial(generate_tissue_mask_in_ref_space, aparc_aseg=names_dict["aparc_aseg"], ref_img=bc_calib_name, struct2ref=struct2asl, order=0) tissues = ("gm", "wm") if roi == "combined" else (roi, ) names = [roi_dir / f"{t}_mask.nii.gz" for t in tissues] if not all(n.exists() for n in names) or force_refresh: if roi == "csf": masks = [ base_mask_call(tissue=t, erode=True) for t in tissues ] else: masks = [base_mask_call(tissue=t) for t in tissues] [nb.save(m, n) for m, n in zip(masks, names)] else: masks = [nb.load(n) for n in names] if ignore_dropouts: # ignore dropout voxels masks = [ nb.nifti1.Nifti1Image( m.get_fdata() * dropouts_inv.get_fdata(), affine=dropouts_inv.affine) for m in masks ] # save [nb.save(m, n) for m, n in zip(masks, names)] # apply tissue masks to bias- and distortion- corrected images calib_masked_names = [ roi_dir / f"{calib_name_stem}_{t}_masked.nii.gz" for t in tissues ] if not all(c.exists() for c in calib_masked_names) or force_refresh: [ fslmaths(str(bc_calib_name)).mul(mask).run(str(name)) for mask, name in zip(masks, calib_masked_names) ] return (subject_dir, 1) except Exception as e: print(e) return (subject_dir, e)
def test_bet(): with asrt.disabled(), run.dryrun(), mockFSLDIR(bin=('bet', )) as fsldir: bet = op.join(fsldir, 'bin', 'bet') result = fw.bet('input', 'output', mask=True, c=(10, 20, 30)) expected = (bet + ' input output', ('-m', '-c 10 20 30')) assert checkResult(result.stdout[0], *expected, stripdir=[2])
def brain(wsp, img, thresh=0.5): """ Extract brain from wholehead image """ bet_result = fsl.bet(img, seg=True, mask=False, fracintensity=thresh, output=fsl.LOAD, log=wsp.fsllog) return bet_result["output"]