Beispiel #1
0
def main():
    path_warp = sys.argv[
        1]  # {StudyDir}/{SubjectID}/MNINonLinear/xfms/acpc_dc2standard.nii.gz
    path_to_T1_space_ASL_variable = sys.argv[
        2]  # {StudyDir}/{SubjectID}/T1w/ASL/TIs/OxfordASL/native_space/<perfusion_variable>
    path_T1 = sys.argv[
        3]  # {StudyDir}/{SubjectID}/T1w/T1w_acpc_dc_restore.nii.gz
    path_MNI = sys.argv[4]  # /usr/local/fsl/data/standard/MNI152_T1_2mm.nii.gz
    path_to_lowres_MNI = sys.argv[
        5]  # {StudyDir}/{SubjectID}/MNINonLinear/ASL/OutputtoCIFTI/asl_grid_mni.nii.gz
    path_to_MNI_space_ASL_variable = sys.argv[
        6]  # {StudyDir}/{SubjectID}/MNINonLinear/ASL/Results/<folder/perfusion_variable_MNI>

    # Make the ASL-grid MNI space target image for registration (if needed)
    if not op.isfile(path_to_lowres_MNI):
        print("Creating ASL-grid MNI-space MNI image")
        perfusion_spc = rt.ImageSpace(path_to_T1_space_ASL_variable)
        mni_spc = rt.ImageSpace(path_MNI)
        mni_asl_grid = mni_spc.resize_voxels(perfusion_spc.vox_size /
                                             mni_spc.vox_size)
        nb.save(
            rt.Registration.identity().apply_to_image(path_MNI, mni_asl_grid),
            path_to_lowres_MNI)
    else:
        print("ASL-grid MNI-space MNI image already exists")

    # Warp ASL variable to newly prepared ASL-gridded MNI-space
    print("Transforming ASL Variable to ASL-gridded MNI-space ASL")
    the_warp = rt.NonLinearRegistration.from_fnirt(path_warp, path_T1,
                                                   path_MNI)
    asl_mni_mniaslgrid = the_warp.apply_to_image(path_to_T1_space_ASL_variable,
                                                 path_to_lowres_MNI)
    nb.save(asl_mni_mniaslgrid, path_to_MNI_space_ASL_variable)
Beispiel #2
0
def main():

    # argument handling
    parser = argparse.ArgumentParser(
        description=
        "Create T1-ASL space, estimate PVs, generate CSF ventricle mask")
    parser.add_argument("study_dir", help="Path of the base study directory.")
    parser.add_argument("sub_number", help="Subject number.")

    args = parser.parse_args()
    study_dir = args.study_dir
    sub_id = args.sub_number

    # for debug, re-use intermediate results
    force_refresh = True

    sub_base = op.abspath(op.join(study_dir, sub_id))
    t1_dir = op.join(sub_base, "T1w")
    asl = op.join(sub_base, "ASL", "TIs", "tis.nii.gz")
    struct = op.join(t1_dir, "T1w_acpc_dc_restore.nii.gz")

    # 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:
        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)
        nib.save(
            rt.Registration.identity().apply_to_image(struct, t1_asl_grid_spc),
            t1_asl_grid)

    # Create a ventricle CSF mask in T1 ASL space
    ventricle_mask = op.join(sub_base, "T1w", "ASL", "PVEs",
                             "vent_csf_mask.nii.gz")
    if not op.exists(ventricle_mask) or force_refresh:
        aparc_aseg = op.join(t1_dir, "aparc+aseg.nii.gz")
        vmask = generate_ventricle_mask(aparc_aseg, t1_asl_grid)
        rt.ImageSpace.save_like(t1_asl_grid, vmask, ventricle_mask)

    # Estimate PVs in T1 ASL space
    pv_gm = op.join(sub_base, "T1w", "ASL", "PVEs", "pve_GM.nii.gz")
    if not op.exists(pv_gm) or force_refresh:
        aparc_seg = op.join(t1_dir, "aparc+aseg.nii.gz")
        pvs_stacked = estimate_pvs(t1_dir, t1_asl_grid)

        # Save output with tissue suffix
        fileroot = op.join(sub_base, "T1w", "ASL", "PVEs", "pve")
        for idx, suffix in enumerate(['GM', 'WM', 'CSF']):
            p = "{}_{}.nii.gz".format(fileroot, suffix)
            rt.ImageSpace.save_like(t1_asl_grid, pvs_stacked.dataobj[..., idx],
                                    p)
Beispiel #3
0
def main():
    desc = """
    Generate PV estimates for a reference voxel grid. FS' volumetric 
    segmentation is used for the subcortex and a surface-based method
    is used for the cortex (toblerone). 
    """

    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument(
        "--aparcseg",
        required=True,
        help="path to volumetric FS segmentation (aparc+aseg.mgz)")
    parser.add_argument("--LWS",
                        required=True,
                        help="path to left white surface")
    parser.add_argument("--LPS",
                        required=True,
                        help="path to left pial surface")
    parser.add_argument("--RPS",
                        required=True,
                        help="path to right pial surface")
    parser.add_argument("--RWS",
                        required=True,
                        help="path to right white surface")
    parser.add_argument("--ref",
                        required=True,
                        help="path to image defining reference grid for PVs")
    parser.add_argument("--out", required=True, help="path to save output")
    parser.add_argument("--stack",
                        action="store_true",
                        help="stack output into single 4D volume")
    parser.add_argument("--cores",
                        default=1,
                        type=int,
                        help="CPU cores to use")

    args = parser.parse_args()
    surf_dict = dict([(k, getattr(args, k))
                      for k in ['LWS', 'LPS', 'RPS', 'RWS']])

    pvs = extract_fs_pvs(args.aparcseg, surf_dict, args.ref, args.cores)

    if args.stack:
        nib.save(pvs, args.out)

    else:
        opath = pathlib.Path(args.out)
        spc = rt.ImageSpace(pvs)
        pvs = pvs.dataobj
        for idx, tiss in enumerate(["GM", "WM", "CSF"]):
            n = opath.parent.joinpath(
                opath.stem.rsplit('.', 1)[0] + f"_{tiss}" +
                "".join(opath.suffixes))
            spc.save_image(pvs[..., idx], n.as_posix())
Beispiel #4
0
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)
Beispiel #5
0
def generate_ventricle_mask(aparc_aseg, t1_asl):

    ref_spc = rt.ImageSpace(t1_asl)

    # get ventricles mask from aparc+aseg image
    aseg = nib.load(aparc_aseg).get_data()
    vent_mask = np.logical_or(
        aseg == 43,  # left ventricle 
        aseg == 4  # right ventricle 
    )

    # erosion in t1 space for safety
    vent_mask = scipy.ndimage.morphology.binary_erosion(vent_mask)

    # Resample to target space, re-threshold
    output = rt.Registration.identity().apply_to_array(vent_mask, aparc_aseg,
                                                       ref_spc)
    output = (output > 0.8)
    return output
Beispiel #6
0
def create_ti_image(asl, tis, sliceband, slicedt, outname):
    """
    Create a 4D series of actual TIs at each voxel.

    Args:
        asl: path to image in the space we wish to create the TI series
        tis: list of TIs in the acquisition
        sliceband: number of slices per band in the acquisition
        slicedt: time taken to acquire each slice
        outname: path to which the ti image is saved
    
    Returns:
        n/a, file outname is created in output directory
    """

    asl_spc = rt.ImageSpace(asl)
    n_slice = asl_spc.size[2]
    slice_in_band = np.tile(np.arange(0, sliceband),
                            n_slice // sliceband).reshape(1, 1, n_slice, 1)
    ti_array = np.array([np.tile(x, asl_spc.size)
                         for x in tis]).transpose(1, 2, 3, 0)
    ti_array = ti_array + (slice_in_band * slicedt)
    rt.ImageSpace.save_like(asl, ti_array, outname)
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)
Beispiel #8
0
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).")
    args = parser.parse_args()
    study_dir = args.study_dir
    sub_num = args.sub_number
    grad_coeffs = args.grads

    oph = (study_dir + "/" + sub_num + "/ASL/TIs/DistCorr")
    outdir = (study_dir + "/" + sub_num + "/T1w/ASL/reg")
    pve_path = (study_dir + "/" + sub_num + "/T1w/ASL/PVEs")
    T1w_oph = (study_dir + "/" + sub_num + "/T1w/ASL/TIs/DistCorr")
    T1w_cal_oph = (study_dir + "/" + sub_num +
                   "/T1w/ASL/Calib/Calib0/DistCorr")
    need_dirs = [oph, outdir, pve_path, T1w_oph, T1w_cal_oph]
    for req_dir in need_dirs:
        Path(req_dir).mkdir(parents=True, exist_ok=True)

    initial_wd = os.getcwd()
    print("Pre-distortion correction working directory was: " + initial_wd)
    print("Changing working directory to: " + oph)
    os.chdir(oph)

    # Generate ASL-gridded T1-aligned T1w image for use as a reg reference
    t1 = (study_dir + "/" + sub_num + "/T1w/T1w_acpc_dc_restore.nii.gz")
    t1_brain = (study_dir + "/" + sub_num +
                "/T1w/T1w_acpc_dc_restore_brain.nii.gz")

    asl = (study_dir + "/" + sub_num +
           "/ASL/TIs/STCorr/SecondPass/tis_stcorr.nii.gz")
    t1_asl_res = (study_dir + "/" + sub_num +
                  "/T1w/ASL/reg/ASL_grid_T1w_acpc_dc_restore.nii.gz")

    asl_v1 = (study_dir + "/" + sub_num +
              "/ASL/TIs/STCorr/SecondPass/tis_stcorr_vol1.nii.gz")
    first_asl_call = ("fslroi " + asl + " " + asl_v1 + " 0 1")
    # print(first_asl_call)
    sp.run(first_asl_call.split(), check=True, stderr=sp.PIPE, stdout=sp.PIPE)

    print("Running regtricks bit")
    t1_spc = rt.ImageSpace(t1)
    asl_spc = rt.ImageSpace(asl_v1)
    t1_spc_asl = t1_spc.resize_voxels(asl_spc.vox_size / t1_spc.vox_size)
    r = rt.Registration.identity()
    t1_asl = r.apply_to_image(t1, t1_spc_asl)
    nb.save(t1_asl, t1_asl_res)
    # Check .grad coefficients are available and call function to generate
    # GDC warp if they are:
    if os.path.isfile(grad_coeffs):
        calc_gdc_warp(asl_v1, grad_coeffs, oph)
    else:
        print("Gradient coefficients not available")

    print("Changing back to original working directory: " + initial_wd)
    os.chdir(initial_wd)

    # output file of topup parameters to subject's distortion correction dir
    pars_filepath = (oph + "/topup_params.txt")
    produce_topup_params(pars_filepath)

    # generate EPI distortion correction fieldmaps for use in asl_reg
    pa_sefm, ap_sefm = find_field_maps(study_dir, sub_num)
    pa_ap_sefms = (oph + "/merged_sefms.nii.gz")
    cnf_file = "b02b0.cnf"
    out_basename = (oph + "/topup_result")
    topup_fmap = (oph + "/topup_result_fmap_hz.nii.gz")
    fmap_rads = (oph + "/fmap_rads.nii.gz")
    fmapmag = (oph + "/fmapmag.nii.gz")
    fmapmagbrain = (oph + "/fmapmag_brain.nii.gz")
    calc_fmaps(pa_sefm, ap_sefm, pa_ap_sefms, pars_filepath, cnf_file, oph,
               out_basename, topup_fmap, fmap_rads, fmapmag, fmapmagbrain)

    # Calculate initial linear transformation from ASL-space to T1w-space
    asl_v1_brain = (study_dir + "/" + sub_num +
                    "/ASL/TIs/STCorr/SecondPass/tis_stcorr_vol1_brain.nii.gz")
    bet_regfrom_call = ("bet " + asl_v1 + " " + asl_v1_brain)
    # print(bet_regfrom_call)
    sp.run(bet_regfrom_call.split(),
           check=True,
           stderr=sp.PIPE,
           stdout=sp.PIPE)

    gen_initial_trans(asl_v1_brain, outdir, t1, t1_brain)

    # Generate a brain mask in the space of the 1st ASL volume
    asl2struct = (outdir + "/asl2struct.mat")
    t1_brain_mask = (outdir + "/T1w_acpc_dc_restore_brain_mask.nii.gz")
    asl_mask = (outdir + "/asl_vol1_mask.nii.gz")
    struct2asl = (outdir + "/struct2asl.mat")

    gen_asl_mask(t1_brain, t1_brain_mask, asl_v1_brain, asl2struct, asl_mask,
                 struct2asl)

    # brain mask
    t1_mask = (study_dir + "/" + sub_num +
               "/T1w/ASL/reg/T1w_acpc_dc_restore_brain_mask.nii.gz")
    t1_asl_mask_name = (
        study_dir + "/" + sub_num +
        "/T1w/ASL/reg/ASL_grid_T1w_acpc_dc_restore_brain_mask.nii.gz")
    t1_mask_spc = rt.ImageSpace(t1_mask)
    t1_mask_spc_asl = t1_mask_spc.resize_voxels(asl_spc.vox_size /
                                                t1_mask_spc.vox_size)
    r = rt.Registration.identity()
    t1_mask_asl = r.apply_to_image(t1_mask, t1_mask_spc_asl)
    fslmaths(t1_mask_asl).thr(0.5).bin().run(t1_asl_mask_name)

    # Generate PVEs
    aparc_aseg = (study_dir + "/" + sub_num + "/T1w/aparc+aseg.nii.gz")
    pve_files = (study_dir + "/" + sub_num + "/T1w/ASL/PVEs/pve")
    gen_pves(Path(t1).parent, asl, pve_files)

    # Generate WM mask
    pvwm = (pve_files + "_WM.nii.gz")
    tissseg = (study_dir + "/" + sub_num + "/T1w/ASL/PVEs/wm_mask.nii.gz")
    gen_wm_mask(pvwm, tissseg)

    # Calculate the overall distortion correction warp
    asl2str_trans = (outdir + "/asl2struct.mat")
    gdc_warp = (oph + "/gdc_warp.nii.gz")
    calc_distcorr_warp(asl_v1_brain, oph, t1, t1_brain, asl_mask, tissseg,
                       asl2str_trans, fmap_rads, fmapmag, fmapmagbrain,
                       t1_asl_res, gdc_warp)

    # Calculate the Jacobian of the distortion correction warp
    calc_warp_jacobian(oph)

    # apply the combined distortion correction warp with motion correction
    # to move asl data, calibrationn images, and scaling factors into
    # ASL-gridded T1w-aligned space

    asl_distcorr = (T1w_oph + "/tis_distcorr.nii.gz")
    moco_xfms = (study_dir + "/" + sub_num + "/ASL/TIs/MoCo/asln2asl0.mat"
                 )  #will this work?
    concat_xfms = str(Path(moco_xfms).parent / f'{Path(moco_xfms).stem}.cat')
    # concatenate xfms like in oxford_asl
    concat_call = f'cat {moco_xfms}/MAT* > {concat_xfms}'
    sp.run(concat_call, shell=True)
    # only correcting and transforming the 1st of the calibration images at the moment
    calib_orig = (study_dir + "/" + sub_num +
                  "/ASL/Calib/Calib0/MTCorr/calib0_mtcorr.nii.gz")
    calib_distcorr = (study_dir + "/" + sub_num +
                      "/T1w/ASL/Calib/Calib0/DistCorr/calib0_dcorr.nii.gz")
    calib_inv_xfm = (study_dir + "/" + sub_num +
                     "/ASL/TIs/MoCo/asln2m0.mat/MAT_0000")
    calib_xfm = (study_dir + "/" + sub_num + "/ASL/TIs/MoCo/calibTOasl1.mat")

    sfacs_orig = (study_dir + "/" + sub_num +
                  "/ASL/TIs/STCorr/SecondPass/combined_scaling_factors.nii.gz")
    sfacs_distcorr = (T1w_oph + "/combined_scaling_factors.nii.gz")

    invert_call = ("convert_xfm -omat " + calib_xfm + " -inverse " +
                   calib_inv_xfm)
    # print(invert_call)
    sp.run(invert_call.split(), check=True, stderr=sp.PIPE, stdout=sp.PIPE)

    apply_distcorr_warp(asl, t1_asl_res, asl_distcorr, oph, concat_xfms,
                        calib_orig, calib_distcorr, calib_xfm, sfacs_orig,
                        sfacs_distcorr)
Beispiel #9
0
def main():

    # argument handling
    parser = argparse.ArgumentParser(
        description=
        "Create T1-ASL space, estimate PVs, generate CSF ventricle mask")
    parser.add_argument("study_dir", help="Path of the base study directory.")
    parser.add_argument("sub_number", help="Subject number.")
    parser.add_argument(
        "-c",
        "--cores",
        help="Number of cores to use when applying motion correction and " +
        "other potentially multi-core operations. Default is 1",
        default=1,
        type=int,
        choices=range(1,
                      mp.cpu_count() + 1))
    parser.add_argument(
        "--outdir",
        help="Name of the directory within which we will store all of the " +
        "pipeline's outputs in sub-directories. Default is 'hcp_asl'",
        default="hcp_asl")
    parser.add_argument(
        "--interpolation",
        help="Interpolation order for registrations. This can be any " +
        "integer from 0-5 inclusive. Default is 3. See scipy's " +
        "map_coordinates for more details.",
        default=3,
        type=int,
        choices=range(0, 5 + 1))

    args = parser.parse_args()
    study_dir = args.study_dir
    sub_id = args.sub_number

    # for debug, re-use intermediate results
    force_refresh = True

    sub_base = op.abspath(op.join(study_dir, sub_id))
    t1_dir = op.join(sub_base, "T1w")
    t1_asl_dir = op.join(sub_base, args.outdir, "T1w", "ASL")
    asl = op.join(sub_base, args.outdir, "ASL", "TIs", "tis.nii.gz")
    struct = op.join(t1_dir, "T1w_acpc_dc_restore.nii.gz")

    # Create ASL-gridded version of T1 image
    t1_asl_grid = op.join(t1_asl_dir, "reg",
                          "ASL_grid_T1w_acpc_dc_restore.nii.gz")
    if not op.exists(t1_asl_grid) or force_refresh:
        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)
        nib.save(
            rt.Registration.identity().apply_to_image(struct, t1_asl_grid_spc),
            t1_asl_grid)

    # Create PVEs directory
    pve_dir = op.join(sub_base, args.outdir, "T1w", "ASL", "PVEs")
    os.makedirs(pve_dir, exist_ok=True)

    # Create a ventricle CSF mask in T1 ASL space
    ventricle_mask = op.join(pve_dir, "vent_csf_mask.nii.gz")
    if not op.exists(ventricle_mask) or force_refresh:
        aparc_aseg = op.join(t1_dir, "aparc+aseg.nii.gz")
        vmask = generate_ventricle_mask(aparc_aseg, t1_asl_grid)
        rt.ImageSpace.save_like(t1_asl_grid, vmask, ventricle_mask)

    # Estimate PVs in ASL0 space then register them to ASLT1w space
    asl2struct = op.join(t1_asl_dir, "TIs", "reg", "asl2struct.mat")
    pv_gm = op.join(pve_dir, "pve_GM.nii.gz")
    if not op.exists(pv_gm) or force_refresh:
        aparc_seg = op.join(t1_dir, "aparc+aseg.nii.gz")
        pvs_stacked = estimate_pvs(t1_dir,
                                   asl,
                                   ref2struct=asl2struct,
                                   cores=args.cores)

        # register the PVEs from ASL0 space to ASLT1w space with the
        # same order of interpolation used to register the ASL series
        asl2struct = rt.Registration.from_flirt(asl2struct, asl, struct)
        pvs_stacked = asl2struct.apply_to_image(src=pvs_stacked,
                                                ref=t1_asl_grid,
                                                order=args.interpolation,
                                                cores=args.cores)

        # Save output with tissue suffix
        fileroot = op.join(pve_dir, "pve")
        for idx, suffix in enumerate(['GM', 'WM', 'CSF']):
            p = "{}_{}.nii.gz".format(fileroot, suffix)
            rt.ImageSpace.save_like(t1_asl_grid, pvs_stacked.dataobj[..., idx],
                                    p)
Beispiel #10
0
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)
Beispiel #11
0
    def enforcer(ref, struct2ref, **kwargs):

        # Reference image path
        if (not isinstance(ref, rt.ImageSpace)):
            ref = rt.ImageSpace(ref)

        # If given a fslanat dir we can load the structural image in
        if kwargs.get('fslanat'):
            if not check_anat_dir(kwargs['fslanat']):
                raise RuntimeError(
                    "fslanat is not complete: it must contain "
                    "FAST output and a first_results subdirectory")

            kwargs['fastdir'] = kwargs['fslanat']
            kwargs['firstdir'] = op.join(kwargs['fslanat'], 'first_results')

            # If no struct image given, try and pull it out from the anat dir
            # But, if it has been cropped relative to original T1, then give
            # warning (as we will not be able to convert FLIRT to world-world)
            if not kwargs.get('struct'):
                if kwargs.get('flirt'):
                    matpath = glob.glob(
                        op.join(kwargs['fslanat'], '*nonroi2roi.mat'))[0]
                    nonroi2roi = np.loadtxt(matpath)
                    if np.any(np.abs(nonroi2roi[0:3, 3])):
                        print(
                            "Warning: T1 was cropped relative to T1_orig within"
                            +
                            " fslanat dir.\n Please ensure the struct2ref FLIRT"
                            + " matrix is referenced to T1, not T1_orig.")

                s = op.join(kwargs['fslanat'], 'T1.nii.gz')
                kwargs['struct'] = s
                if not op.isfile(s):
                    raise RuntimeError(
                        "Could not find T1.nii.gz in the fslanat dir")

        # Structural to reference transformation. Either as array, path
        # to file containing matrix, or regtricks Registration object
        if not any([
                type(struct2ref) is str,
                type(struct2ref) is np.ndarray,
                type(struct2ref) is rt.Registration
        ]):
            raise RuntimeError(
                "struct2ref transform must be given (either path,",
                " np.array or regtricks Registration object)")

        else:
            s2r = struct2ref

            if (type(s2r) is str):
                if s2r == 'I':
                    matrix = np.identity(4)
                else:
                    _, matExt = op.splitext(s2r)

                    try:
                        if matExt in ['.txt', '.mat']:
                            matrix = np.loadtxt(s2r, dtype=NP_FLOAT)
                        elif matExt in ['.npy', 'npz', '.pkl']:
                            matrix = np.load(s2r)
                        else:
                            matrix = np.fromfile(s2r, dtype=NP_FLOAT)

                    except Exception as e:
                        warnings.warn("""Could not load struct2ref matrix. 
                            File should be any type valid with numpy.load()."""
                                      )
                        raise e

                struct2ref = matrix

        # If FLIRT transform we need to do some clever preprocessing
        # We then set the flirt flag to false again (otherwise later steps will
        # repeat the tricks and end up reverting to the original - those steps don't
        # need to know what we did here, simply that it is now world-world again)
        if kwargs.get('flirt'):
            if not kwargs.get('struct'):
                raise RuntimeError(
                    "If using a FLIRT transform, the path to the"
                    " structural image must also be given")

            struct2ref = rt.Registration.from_flirt(struct2ref,
                                                    kwargs['struct'],
                                                    ref).src2ref
            kwargs['flirt'] = False
        elif isinstance(struct2ref, rt.Registration):
            struct2ref = struct2ref.src2ref

        assert isinstance(struct2ref,
                          np.ndarray), 'should have cast struc2ref to np.array'

        # Processor cores
        if not kwargs.get('cores'):
            kwargs['cores'] = multiprocessing.cpu_count()

        # Supersampling factor
        sup = kwargs.get('super')
        if sup is not None:
            try:
                if (type(sup) is list) and (len(sup) == 3):
                    sup = np.array([int(s) for s in sup])
                else:
                    sup = int(sup[0])
                    sup = np.array([sup for _ in range(3)])

                if type(sup) is not np.ndarray:
                    raise RuntimeError()
            except:
                raise RuntimeError("-super must be a value or list of 3" +
                                   " values of int type")

        return ref, struct2ref, kwargs
Beispiel #12
0
def extract_fs_pvs(aparcseg, surf_dict, ref_spc, ref2struct=None, cores=1):
    """
    Extract and layer PVs according to tissue type, taken from a FS aparc+aseg. 
    Results are stored in ASL-gridded T1 space. 
    Args:
        aparcseg: path to aparc+aseg file
        surf_dict: dict with LWS/LPS/RWS/RPS keys, paths to those surfaces
        ref_spc: space in which to estimate (ie, ASL-gridded T1)
        superfactor: supersampling factor for intermediate steps
        cores: number CPU cores to use, default is 1
    Returns: 
        nibabel Nifti object 
    """

    ref_spc = rt.ImageSpace(ref_spc)
    aseg_spc = nib.load(aparcseg)
    aseg = aseg_spc.dataobj
    aseg_spc = rt.ImageSpace(aseg_spc)

    # Estimate cortical PVs, loading a struct2ref registration if provided
    # If not provided, an identity transform is used
    if ref2struct:
        struct2ref_reg = rt.Registration.from_flirt(ref2struct, ref_spc,
                                                    aseg_spc).inverse()
        cortex = estimate_cortex(ref=ref_spc,
                                 struct2ref=struct2ref_reg.src2ref,
                                 cores=cores,
                                 **surf_dict)
    else:
        struct2ref_reg = rt.Registration.identity()
        cortex = estimate_cortex(ref=ref_spc,
                                 struct2ref='I',
                                 cores=cores,
                                 **surf_dict)

    # Extract PVs from aparcseg segmentation. Subcortical structures go into
    # a dict keyed according to their name, whereas general WM/GM are
    # grouped into the vol_pvs array
    to_stack = {}
    vol_pvs = np.zeros((aseg_spc.size.prod(), 3), dtype=np.float32)
    for label in np.unique(aseg):
        tissue = SUBCORT_LUT.get(label)
        if not tissue:
            tissue = CTX_LUT(label)
        if tissue:
            # print(f"Label {label} assigned to {tissue}.")
            mask = (aseg == label)
            if tissue == "WM":
                vol_pvs[mask.flatten(), 1] = 1
            elif tissue == "GM":
                vol_pvs[mask.flatten(), 0] = 1
            elif tissue == "CSF":
                pass
            else:
                to_stack[tissue] = mask.astype(np.float32)
        elif label not in IGNORE:
            print("Did not assign tissue for aseg/aparc label:", label)

    # Super-resolution resampling for the vol_pvs, a la applywarp.
    # 0: GM, 1: WM, 2: CSF, always in the LAST dimension of an array
    vol_pvs = struct2ref_reg.apply_to_array(vol_pvs.reshape(*aseg.shape, 3),
                                            aseg_spc,
                                            ref_spc,
                                            order=1,
                                            cores=cores)
    vol_pvs = vol_pvs.reshape(-1, 3)
    vol_pvs[:, 2] = np.maximum(0, 1 - vol_pvs[:, :2].sum(1))
    vol_pvs[vol_pvs[:, 2] < 1e-2, 2] = 0
    vol_pvs /= vol_pvs.sum(1)[:, None]
    vol_pvs = vol_pvs.reshape(*ref_spc.size, 3)

    # Stack the subcortical structures into a big 4D volume to resample them
    # all at once, then put them back into a dict
    keys, values = list(to_stack.keys()), list(to_stack.values())
    del to_stack
    subcorts = struct2ref_reg.apply_to_array(np.stack(values, axis=-1),
                                             aseg_spc,
                                             ref_spc,
                                             order=1,
                                             cores=cores)
    subcorts = np.moveaxis(subcorts, 3, 0)
    to_stack = dict(zip(keys, subcorts))

    # Add the cortical and vol PV estimates into the dict, stack them in
    # a sneaky way (see the stack_images function)
    to_stack['cortex_GM'] = cortex[..., 0]
    to_stack['cortex_WM'] = cortex[..., 1]
    to_stack['cortex_nonbrain'] = cortex[..., 2]
    to_stack['vol_GM'] = vol_pvs[..., 0]
    to_stack['vol_WM'] = vol_pvs[..., 1]
    to_stack['vol_CSF'] = vol_pvs[..., 2]
    result = stack_images(to_stack)

    return ref_spc.make_nifti(result.reshape((*ref_spc.size, 3)))
Beispiel #13
0
def extract_fs_pvs(aparcseg, surf_dict, t1, asl, superfactor=2, 
                   cores=mp.cpu_count()): 
    """
    Extract and layer PVs according to tissue type, taken from a FS aparc+aseg. 
    Results are stored in ASL-gridded T1 space. 
    Args:
        aparcseg: path to aparc+aseg file
        surf_dict: dict with LWS/LPS/RWS/RPS keys, paths to those surfaces
        t1: path to T1 file
        asl: path to ASL file (for setting resolution)
        superfactor: supersampling factor for intermediate steps
        cores: number CPU cores to use 
    Returns: 
        nibabel Nifti object 
    """

    asl_spc = rt.ImageSpace(asl)
    t1_spc = rt.ImageSpace(t1)
    ref_spc = t1_spc.resize_voxels(asl_spc.vox_size / t1_spc.vox_size)
    high_spc = ref_spc.resize_voxels(1/superfactor, 'ceil')
    aseg_spc = nib.load(aparcseg)
    aseg = aseg_spc.dataobj
    aseg_spc = rt.ImageSpace(aseg_spc)

    # Estimate cortical PVs 
    # FIXME: allow tob to accept imagespace directly here
    with tempfile.TemporaryDirectory() as td:
        ref_path = op.join(td, 'ref.nii.gz')
        ref_spc.touch(ref_path)
        cortex = estimate_cortex(ref=ref_path, struct2ref='I', 
            superfactor=1, cores=cores, **surf_dict)

    # Extract PVs from aparcseg segmentation. Subcortical structures go into 
    # a dict keyed according to their name, whereas general WM/GM are 
    # grouped into the vol_pvs array
    to_stack = {}
    vol_pvs = np.zeros((aseg_spc.size.prod(), 3), dtype=np.float32)
    for label in np.unique(aseg):
        tissue = SUBCORT_LUT.get(label)
        if not tissue: 
            tissue = CTX_LUT(label)
        if tissue: 
            mask = (aseg == label) 
            if tissue == "WM":
                vol_pvs[mask.flatten(),1] = 1
            elif tissue == "GM":
                vol_pvs[mask.flatten(),0] = 1 
            elif tissue == "CSF":
                pass 
            else: 
                to_stack[tissue] = mask.astype(np.float32)
        elif label not in IGNORE: 
            print("Did not assign aseg/aparc label:", label)

    # Super-resolution resampling for the vol_pvs, a la applywarp. 
    # We use an identity transform as we don't actually want to shift the data 
    # 0: GM, 1: WM, 2: CSF, always in the LAST dimension of an array 
    reg = rt.Registration.identity()
    vol_pvs = reg.apply_to_array(vol_pvs.reshape(*aseg.shape, 3), 
                                 aseg_spc, high_spc, order=1, cores=cores)
    vol_pvs = (_sum_array_blocks(vol_pvs, 3 * [superfactor] + [1]) 
               / (superfactor ** 3))
    vol_pvs = vol_pvs.reshape(-1,3)
    vol_pvs[:,2] = np.maximum(0, 1 - vol_pvs[:,:2].sum(1))
    vol_pvs[vol_pvs[:,2] < 1e-2, 2] = 0 
    vol_pvs /= vol_pvs.sum(1)[:,None]
    vol_pvs = vol_pvs.reshape(*ref_spc.size, 3)

    # Stack the subcortical structures into a big 4D volume to resample them
    # all at once, then put them back into a dict 
    keys, values = list(to_stack.keys()), list(to_stack.values())
    del to_stack
    subcorts = reg.apply_to_array(np.stack(values, axis=-1), 
                                  aseg_spc, high_spc, order=1, cores=cores)
    subcorts = (_sum_array_blocks(subcorts, 3 * [superfactor] + [1]) 
                                / (superfactor ** 3))
    subcorts = np.moveaxis(subcorts, 3, 0)
    to_stack = dict(zip(keys, subcorts))

    # Add the cortical and vol PV estimates into the dict, stack them in 
    # a sneaky way (see the stack_images function)
    to_stack['cortex_GM'] = cortex[...,0]
    to_stack['cortex_WM'] = cortex[...,1]
    to_stack['cortex_nonbrain'] = cortex[...,2]
    to_stack['vol_GM'] = vol_pvs[...,0]    
    to_stack['vol_WM'] = vol_pvs[...,1]    
    to_stack['vol_CSF'] = vol_pvs[...,2]
    result = stack_images(to_stack)

    # Squash small values (looks like dodgy output otherwise, but
    # is actually just an artefact of resampling)
    pvmin = result.min()
    if pvmin > 1e-9:
        result[result < 1e-6] = 0 

    return ref_spc.make_nifti(result.reshape((*ref_spc.size, 3))) 
Beispiel #14
0
    sys.argv[1:] = cmd.split()

    parser = argparse.ArgumentParser(usage=usage)
    parser.add_argument("--aparcseg", required=True)
    parser.add_argument("--LWS", required=True)
    parser.add_argument("--LPS", required=True)
    parser.add_argument("--RPS", required=True)
    parser.add_argument("--RWS", required=True)
    parser.add_argument("--t1", required=True)
    parser.add_argument("--asl", required=True)
    parser.add_argument("--out", required=True)
    parser.add_argument("--stack", action="store_true")
    parser.add_argument("--super", default=2, type=int)
    parser.add_argument("--cores", default=mp.cpu_count(), type=int)

    args = parser.parse_args(sys.argv[1:])
    surf_dict = dict([ (k, getattr(args, k)) for k in ['LWS', 'LPS', 'RPS', 'RWS'] ])

    pvs = extract_fs_pvs(args.aparcseg, surf_dict, args.t1, 
                         args.asl, args.super, args.cores)

    if args.stack: 
        nib.save(pvs, args.out)

    else: 
        opath = pathlib.Path(args.out)
        spc = rt.ImageSpace(pvs)
        pvs = pvs.dataobj
        for idx,tiss in enumerate(["GM", "WM", "CSF"]):
            n = opath.parent.joinpath(opath.stem.rsplit('.', 1)[0] + f"_{tiss}" + "".join(opath.suffixes))
            spc.save_image(pvs[...,idx], n.as_posix())