def test_convertwarp(): with asrt.disabled(), run.dryrun(), mockFSLDIR( bin=('convertwarp', )) as fsldir: cnvwarp = op.join(fsldir, 'bin', 'convertwarp') result = fw.convertwarp('out', 'ref', absout=True, jacobian='jacobian') expected = (cnvwarp + ' --ref=ref --out=out', ('--absout', '--jacobian=jacobian')) assert checkResult(result.stdout[0], *expected)
def get_fieldmap_correction(wsp): """ Get the fieldmap based distortion correction warp Required workspace attributes ----------------------------- - ``pwi`` : Perfusion weighted image (generated by preproc_asl) - ``fmap`` : Fieldmap image - ``fmapmag`` : Fieldmap magnitude image - ``fmapmagbrain`` : Fieldmap magnitude brain image - ``echospacing`` : - ``pedir`` : Optional workspace attributes ----------------------------- - ``nofmapreg`` : If True assume fieldmap in structural space Updated workspace attributes ---------------------------- - ``fmap_warp`` : Fieldmap distortion correction warp image in ASL space """ if wsp.fmap is None or wsp.fmapmag is None or wsp.fmapmagbrain is None: wsp.log.write(" - No fieldmap images for distortion correction\n") return elif wsp.pedir is None or wsp.echospacing is None: wsp.log.write( " -WARNING: Fieldmap images supplied but pedir and echospacing required for distortion correction\n" ) return wsp.sub("fieldmap") wsp.log.write( " - Calculating distortion correction from fieldmap images using EPI_REG\n" ) epi_reg_opts = { "inweight": wsp.inweight, "init": wsp.reg.asl2struc, "fmap": wsp.fmap, "fmapmag": wsp.fmapmag, "fmapmagbrain": wsp.fmapmagbrain, "pedir": wsp.pedir, "echospacing": wsp.echospacing, "nofmapreg": wsp.ifnone("nofmapreg", False), } # Windows can't run epi_reg as it's a batch script. Use our experimental python # implementation but use the standard epi_reg on other platforms until the python # version is better tested if sys.platform.startswith("win"): import oxasl.epi_reg as pyepi result = pyepi.epi_reg(wsp, epi=wsp.reg.nativeref, **epi_reg_opts) else: result = epi_reg(epi=wsp.asldata.perf_weighted(), t1=wsp.structural.struc, t1brain=wsp.structural.brain, out=fsl.LOAD, wmseg=wsp.structural.wm_seg, log=wsp.fsllog, **epi_reg_opts) # Occasionally we end up with NaN in the output of epi_reg and this will ruin the entire distcorr warp. # So remove any NaN values and replace with zero. warp_struc = result["out_warp"] wsp.fieldmap.warp_struc = Image(np.nan_to_num(warp_struc.data, nan=0), header=warp_struc.header) wsp.fieldmap.asl2struc = result["out"] wsp.fieldmap.struc2asl = np.linalg.inv(wsp.fieldmap.asl2struc) result = fsl.convertwarp(out=fsl.LOAD, ref=wsp.reg.nativeref, warp1=wsp.fieldmap.warp_struc, postmat=wsp.fieldmap.struc2asl, rel=True, log=wsp.fsllog) wsp.fieldmap.warp = result["out"] page = wsp.report.page("fmap") page.heading("Fieldmap distortion correction", level=0) page.text("PE direction: %s" % wsp.pedir) page.text("Echo spacing: %f s" % wsp.echospacing) page.heading("Correction warps", level=1) for dim in range(3): img = Image(wsp.fieldmap.warp.data[..., dim], header=wsp.fieldmap.warp.header) page.text("Dimension %i" % dim) page.image("fmap_warp%i" % dim, LightboxImage(img))
def run(wsp): """ Apply distortion and motion corrections to ASL and calibration data Required workspace attributes ----------------------------- - ``asldata_orig`` : Uncorrected ASL data image Optional workspace attributes ----------------------------- - ``calib_orig`` : Calibration image - ``cref_orig`` : Calibration reference image - ``cblip_orig`` : Calibration BLIP image - ``asldata_mc_mats`` : ASL motion correction matrices - ``calib2asl`` : Calibration -> ASL transformation matrix - ``distcorr_warp`` : Distortion correction warp image - ``gdc_warp`` : Gradient distortion correction warp image Updated workspace attributes ---------------------------- - ``asldata`` : Corrected ASL data - ``calib`` : Corrected calibration image - ``cref`` : Corrected calibration reference image - ``cblip`` : Corrected calibration BLIP image """ wsp.sub("corrected") wsp.log.write("\nApplying data corrections\n") warps, moco_mats = [], None if wsp.moco is not None: wsp.log.write(" - Using motion correction\n") moco_mats = wsp.moco.mc_mats if wsp.distcorr is not None: if wsp.distcorr.fieldmap is not None: wsp.log.write(" - Using fieldmap distortion correction\n") warps.append(wsp.distcorr.fieldmap.warp) if wsp.distcorr.gdc_warp: wsp.log.write(" - Using user-supplied GDC warp\n") warps.distcorr.append(wsp.gdc_warp) if warps: kwargs = {} for idx, warp in enumerate(warps): kwargs["warp%i" % (idx + 1)] = warp wsp.log.write( " - Converting all warps to single transform and extracting Jacobian\n" ) result = fsl.convertwarp(ref=wsp.reg.nativeref, out=fsl.LOAD, rel=True, jacobian=fsl.LOAD, log=wsp.fsllog, **kwargs) wsp.corrected.total_warp = result["out"] # Calculation of the jacobian for the warp - method suggested in: # https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=FSL;d3fee1e5.0908 wsp.corrected.warp_coef = fnirtfileutils(wsp.corrected.total_warp, outformat="spline", out=fsl.LOAD, log=wsp.fsllog)["out"] jacobian = fnirtfileutils(wsp.corrected.warp_coef, jac=fsl.LOAD, log=wsp.fsllog)["jac"] wsp.corrected.jacobian = Image(jacobian.data, header=wsp.corrected.total_warp.header) if not warps and moco_mats is None: wsp.log.write(" - No corrections to apply to ASL data\n") wsp.corrected.asldata = wsp.preproc.asldata else: # Apply all corrections to ASL data - note that we make sure the output keeps all the ASL metadata wsp.log.write(" - Applying corrections to ASL data\n") asldata_corr = correct_img(wsp, wsp.preproc.asldata, moco_mats) wsp.corrected.asldata = wsp.preproc.asldata.derived(asldata_corr.data) if wsp.preproc.calib is not None: # Apply corrections to calibration images if we have calib2asl registration and any other correction if not warps and wsp.reg is None: wsp.log.write(" - No corrections to apply to calibration data\n") wsp.corrected.calib = wsp.preproc.calib if wsp.cref is not None: wsp.corrected.cref = wsp.preproc.cref if wsp.cblip is not None: wsp.corrected.cblip = wsp.preproc.cblip else: wsp.log.write(" - Applying corrections to calibration data\n") wsp.corrected.calib = correct_img(wsp, wsp.preproc.calib, wsp.reg.calib2asl) if wsp.cref is not None: wsp.corrected.cref = correct_img(wsp, wsp.preproc.cref, wsp.reg.calib2asl) if wsp.cblip is not None: wsp.corrected.cblip = correct_img(wsp, wsp.preproc.cblip, wsp.reg.calib2asl) if wsp.distcorr is not None and wsp.distcorr.topup is not None: wsp.log.write(" - Adding TOPUP distortion correction\n") # This can't currently be done using the FSL wrappers - we need the TOPUP output as two prefixed files # Only workaround currently is to create a temp directory to store appropriately named input files topup_input = tempfile.mkdtemp(prefix="topup_input") try: wsp.distcorr.topup.fieldcoef.save("%s/topup_fieldcoef" % topup_input) movpar_file = open("%s/topup_movpar.txt" % topup_input, "w") for row in wsp.distcorr.topup.movpar: movpar_file.write("\t".join([str(val) for val in row]) + "\n") movpar_file.close() # TOPUP does not do the jacobian magntiude correction - so only okay if using voxelwise calibration wsp.corrected.calib = fsl.applytopup( wsp.corrected.calib, datain=wsp.distcorr.topup.params, index=1, topup="%s/topup" % topup_input, out=fsl.LOAD, method="jac", log=wsp.fsllog)["out"] if wsp.cref is not None: wsp.corrected.cref = fsl.applytopup( wsp.corrected.cref, datain=wsp.distcorr.topup.params, index=1, topup="%s/topup" % topup_input, out=fsl.LOAD, method="jac", log=wsp.fsllog)["out"] if wsp.cblip is not None: wsp.corrected.cblip = fsl.applytopup( wsp.corrected.cblip, datain=wsp.distcorr.topup.params, index=2, topup="%s/topup" % topup_input, out=fsl.LOAD, method="jac", log=wsp.fsllog)["out"] post_topup = fsl.applytopup(wsp.corrected.asldata, datain=wsp.distcorr.topup.params, index=1, topup="%s/topup" % topup_input, out=fsl.LOAD, method="jac", log=wsp.fsllog)["out"] wsp.corrected.asldata = wsp.corrected.asldata.derived( post_topup.data) # FIXME warning below # FIXME do we need to correct anything else in ASL or calibration space, e.g. mask, reference region mask #if wsp.calib_method != "voxel": # wsp.log.write("WARNING: Using TOPUP does not correct for magntiude using the jocbian in distortion correction") # wsp.log.write(" This is not optimal when not using voxelwise calibration\n") # wsp.log.write(" To avoid this supply structural image(s)\n") finally: shutil.rmtree(topup_input)
def epi_reg(wsp, epi_img, use_fmap=False, **kwargs): """ Do EPI registration """ struc.segment(wsp) wmseg = wsp.structural.wm_seg bbr_sch = os.path.join(os.environ["FSLDIR"], "etc/flirtsch/bbr.sch") if wsp.asl2struc is None: # Do pre-alignment in the same was as epi_reg wsp.asl2struc = fsl.flirt(epi_img, ref=wsp.structural.brain, omat=fsl.LOAD, dof=6, log=wsp.fsllog)["omat"] if not use_fmap: wsp.log.write(" - Running BBR\n") trans = fsl.flirt(epi_img, ref=wsp.structural.struc, dof=6, cost="bbr", wmseg=wmseg, init=wsp.asl2struc, omat=fsl.LOAD, out=fsl.LOAD, schedule=bbr_sch, log=wsp.fsllog)["omat"] out_img = fsl.applywarp(epi_img, ref=wsp.structural.struc, out=fsl.LOAD, premat=trans, interp="spline", log=wsp.fsllog)["out"] return {"out.nii.gz": out_img, "out": trans} else: dirmap = { "x": (1, "x"), "y": (2, "y"), "z": (3, "z"), "-x": (-1, "x-"), "-y": (-2, "y-"), "-z": (-3, "z-"), "x-": (-1, "x-"), "y-": (-2, "y-"), "z-": (-3, "z-"), } pedir, fdir = dirmap.get(wsp.pedir, (None, None)) if pedir is None: raise ValueError("Invalid phase encode direction specified: %s" % wsp.pedir) if wsp.nofmapreg: wsp.fmap2struc = np.identity(4) wsp.fmapmag_struc = wsp.fmapmag else: # Register fmap to structural image wsp.log.write(" - Registering fieldmap to structural\n") wsp.fmap2struc = fsl.flirt(wsp.fmapmagbrain, ref=wsp.structural.brain, dof=6, omat=fsl.LOAD)["omat"] flirt_result = fsl.flirt(wsp.fmapmag, ref=wsp.structural.struc, dof=6, init=wsp.fmap2struc, omat=fsl.LOAD, out=fsl.LOAD, nosearch=True) wsp.fmap2struc = flirt_result["omat"] wsp.fmapmag_struc = flirt_result["out"] # Unmask the fieldmap (necessary to avoid edge effects) wsp.fmap_mask = fsl.fslmaths(wsp.fmapmagbrain).abs().bin().run() wsp.fmap_mask = fsl.fslmaths(wsp.fmap).abs().bin().mul( wsp.fmap_mask).run() # The direction here should take into account the initial affine (it needs to be the direction in the EPI) wsp.fmap_unmasked = fsl.fugue(loadfmap=wsp.fmap, mask=wsp.fmap_mask, unmaskfmap=True, savefmap=fsl.LOAD, unwarpdir=fdir)["out"] # The following is a NEW HACK to fix extrapolation when fieldmap is too small wsp.fmap_struc_pad0 = fsl.applywarp(wsp.fmap_unmasked, ref=wsp.structural.struc, premat=wsp.fmap2struc, out=fsl.LOAD)["out"] wsp.fmap_struc_innermask = fsl.fslmaths( wsp.fmap_struc_pad0).abs().bin().run() wsp.fmap_struc_dilated = fsl.fugue(loadfmap=wsp.fmap_struc_pad0, mask=wsp.fmap_struc_innermask, unmaskfmap=True, unwarpdir=fdir, savefmap=fsl.LOAD)["savefmap"] wsp.fmap_struc = wsp.fmap_struc_dilated # Run bbr with fieldmap wsp.log.write("Running BBR with fieldmap\n") if not wsp.epi_reg_use_weighting: refweight = None wsp.reg.epi2struc = fsl.flirt(epi_img, ref=wsp.structural.struc, dof=6, cost="bbr", wmseg=wmseg, init=wsp.reg.asl2struc, omat=fsl.LOAD, schedule=bbr_sch, echospacing=wsp.echospacing, pedir=pedir, fieldmap=wsp.fmap_struc, refweight=refweight)["omat"] # Make equivalent warp fields wsp.log.write( "Making warp fields and applying registration to EPI series\n") wsp.reg.struc2epi = np.linalg.inv(wsp.reg.epi2struc) fsl.concatxfm(wsp.reg.struc2epi, wsp.fmap2struc, outmat=fsl.LOAD) fsl.applywarp(wsp.fmap, ref=epi_img, premat=wsp.fmap2epi, out=fsl.LOAD) wsp.fmap2epi_mask = fsl.fslmaths(wsp.fmap2epi).abs().bin().run() # ${vout}_fieldmaprads2epi -abs -bin ${vout}_fieldmaprads2epi_mask ret = fsl.fugue(loadfmap=wsp.fmap2epi, mask=wsp.fmap2epi_mask, saveshift=fsl.LOAD, unmaskshift=True, dwell=wsp.dwell, unwarpdir=fdir) print(ret) wsp.fmap2epi_shift = ret["fieldmaprads2epi_shift"] ret = fsl.convertwarp(wsp.structural.struc, s=wsp.fmap2epi_shift, postmat=wsp.epi2struc, out=fsl.LOAD, shiftdir=fdir, relout=True) print(ret) warp = ret["out"] ret = fsl.applywarp(epi_img, ref=wsp.structural.struc, out=fsl.LOAD, warp1=warp, interp="spline", rel=True) print(ret) return ret["out"], warp