diff --git a/Dockerfile b/Dockerfile index ad69d492a..e535b9c50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM pennlinc/aslprep_build:main +FROM pennlinc/aslprep_build:0.0.2 # Install aslprep COPY . /src/aslprep diff --git a/aslprep/interfaces/utility.py b/aslprep/interfaces/utility.py index e92d0bb33..a02d401a9 100644 --- a/aslprep/interfaces/utility.py +++ b/aslprep/interfaces/utility.py @@ -43,7 +43,7 @@ def _run_interface(self, runtime): asl_img = nb.load(self.inputs.asl_file) assert asl_img.shape[3] == aslcontext.shape[0] - if self.inputs.processing_target == "controllabel": + if self.inputs.processing_target == "control": files_to_keep = ["control", "label", "m0scan"] elif self.inputs.processing_target == "deltam": files_to_keep = ["deltam", "m0scan"] diff --git a/aslprep/utils/asl.py b/aslprep/utils/asl.py index 95d2d6336..783380a20 100644 --- a/aslprep/utils/asl.py +++ b/aslprep/utils/asl.py @@ -52,7 +52,7 @@ def select_processing_target(aslcontext): voltypes = aslcontext_df["volume_type"].tolist() if "control" in voltypes and "label" in voltypes: - processing_target = "controllabel" + processing_target = "control" elif "deltam" in voltypes: processing_target = "deltam" elif "cbf" in voltypes: diff --git a/aslprep/workflows/asl/base.py b/aslprep/workflows/asl/base.py index 54b7578c9..8bfd282b1 100644 --- a/aslprep/workflows/asl/base.py +++ b/aslprep/workflows/asl/base.py @@ -151,7 +151,7 @@ def init_asl_preproc_wf(asl_file): spaces = config.workflow.spaces output_dir = str(config.execution.output_dir) dummyvols = config.workflow.dummy_vols - smoothkernel = config.workflow.smooth_kernel + smooth_kernel = config.workflow.smooth_kernel m0_scale = config.workflow.m0_scale scorescrub = config.workflow.scorescrub basil = config.workflow.basil @@ -280,7 +280,10 @@ def init_asl_preproc_wf(asl_file): workflow.connect([(inputnode, validate_asl_wf, [("asl_file", "inputnode.asl_file")])]) # Generate a tentative aslref from the most appropriate available image type in the ASL file - asl_reference_wf = init_asl_reference_wf(name="asl_reference_wf") + asl_reference_wf = init_asl_reference_wf( + aslcontext=run_data["aslcontext"], + name="asl_reference_wf", + ) asl_reference_wf.inputs.inputnode.dummy_scans = 0 # fmt:off @@ -432,7 +435,7 @@ def init_asl_preproc_wf(asl_file): m0_scale=m0_scale, scorescrub=scorescrub, basil=basil, - smooth_kernel=smoothkernel, + smooth_kernel=smooth_kernel, metadata=metadata, name="compute_cbf_wf", ) diff --git a/aslprep/workflows/asl/cbf.py b/aslprep/workflows/asl/cbf.py index d8920d357..3b00a0118 100644 --- a/aslprep/workflows/asl/cbf.py +++ b/aslprep/workflows/asl/cbf.py @@ -60,7 +60,7 @@ def init_compute_cbf_wf( wf = init_compute_cbf_wf( name_source=str(perf_dir / "sub-01_asl.nii.gz"), - processing_target="controllabel", + processing_target="control", metadata=metadata, dummy_vols=0, scorescrub=True, @@ -111,6 +111,7 @@ def init_compute_cbf_wf( """ + m0type = metadata["M0Type"] is_casl = pcasl_or_pasl(metadata=metadata) is_multi_pld = determine_multi_pld(metadata=metadata) if (processing_target == "cbf") and not basil: @@ -122,6 +123,26 @@ def init_compute_cbf_wf( ) basil = False + if m0type in ("Included", "Separate"): + m0_str = ( + "Calibration (M0) volumes associated with the ASL scan were smoothed with a " + f"Gaussian kernel (FWHM={smooth_kernel} mm) and the average calibration image was " + f"calculated and scaled by {m0_scale}." + ) + elif m0type == "Estimate": + m0_str = ( + f"A single M0 estimate of {metadata['M0Estimate']} was used to produce a calibration " + f"'image' and was scaled by {m0_scale}." + ) + else: + m0_str = ( + f"As no calibration images or provided M0 estimate was available for the ASL scan, " + "the control volumes used as a substitute. " + "The control volumes in the ASL scans were smoothed with a " + f"Gaussian kernel (FWHM={smooth_kernel} mm) and the average control image was " + f"calculated and scaled by {m0_scale}." + ) + if processing_target == "cbf": workflow.__desc__ += """\ *ASLPrep* loaded pre-calculated cerebral blood flow (CBF) data from the ASL file. @@ -134,6 +155,7 @@ def init_compute_cbf_wf( {metadata['ArterialSpinLabelingType']} data using the following method. First, delta-M values were averaged over time for each post-labeling delay (PLD). +{m0_str} Next, arterial transit time (ATT) was estimated on a voxel-wise basis according to @dai2012reduced. @@ -151,6 +173,7 @@ def init_compute_cbf_wf( *ASLPrep* calculated cerebral blood flow (CBF) from the single-delay {metadata['ArterialSpinLabelingType']} using a single-compartment general kinetic model [@buxton1998general]. +{m0_str} """ else: @@ -163,22 +186,25 @@ def init_compute_cbf_wf( # Single-delay PASL data, with different bolus cut-off techniques if bcut == "QUIPSS": - workflow.__desc__ += """\ + workflow.__desc__ += f"""\ *ASLPrep* calculated cerebral blood flow (CBF) from the single-delay PASL using a single-compartment general kinetic model [@buxton1998general] using the QUIPSS modification, as described in @wong1998quantitative. +{m0_str} """ elif bcut == "QUIPSSII": - workflow.__desc__ += """\ + workflow.__desc__ += f"""\ *ASLPrep* calculated cerebral blood flow (CBF) from the single-delay PASL using a single-compartment general kinetic model [@buxton1998general] using the QUIPSS II modification, as described in @alsop_recommended_2015. +{m0_str} """ elif bcut == "Q2TIPS": - workflow.__desc__ += """\ + workflow.__desc__ += f"""\ *ASLPrep* calculated cerebral blood flow (CBF) from the single-delay PASL using a single-compartment general kinetic model [@buxton1998general] using the Q2TIPS modification, as described in @noguchi2015technical. +{m0_str} """ else: # No bolus cutoff delay technique @@ -190,11 +216,6 @@ def init_compute_cbf_wf( "basis based on the slice timing." ) - if m0_scale != 1: - workflow.__desc__ += ( - f"Prior to calculating CBF, the M0 volumes were scaled by a factor of {m0_scale}." - ) - inputnode = pe.Node( niu.IdentityInterface( fields=[ @@ -533,11 +554,6 @@ def init_compute_cbf_ge_wf( "basis based on the slice timing." ) - if m0_scale != 1: - workflow.__desc__ += ( - f"Prior to calculating CBF, the M0 volumes were scaled by a factor of {m0_scale}." - ) - inputnode = pe.Node( niu.IdentityInterface( fields=[ diff --git a/aslprep/workflows/asl/ge_utils.py b/aslprep/workflows/asl/ge_utils.py index 2e7804d70..b1c48fe84 100644 --- a/aslprep/workflows/asl/ge_utils.py +++ b/aslprep/workflows/asl/ge_utils.py @@ -10,6 +10,7 @@ from aslprep import config from aslprep.interfaces import DerivativesDataSink from aslprep.interfaces.ge import GeReferenceFile +from aslprep.utils.asl import select_processing_target from aslprep.workflows.asl.registration import init_fsl_bbr_wf DEFAULT_MEMORY_MIN_GB = config.DEFAULT_MEMORY_MIN_GB @@ -50,6 +51,32 @@ def init_asl_reference_ge_wf( First, a reference volume and its skull-stripped version were generated. """ + m0type = metadata["M0Type"] + processing_target = select_processing_target(aslcontext=aslcontext) + if m0type in ("Included", "Separate"): + ref_str = ( + "The reference volume was generated by extracting M0 volumes associated with the ASL " + "data, averaging the M0 volumes, and smoothing the mean image with a Gaussian kernel " + f"(FWHM={smooth_kernel} mm). This smoothed M0 volume was retained for later use in " + "CBF calculation as well. " + ) + elif m0type == "Estimate": + ref_str = ( + f"The reference volume was generated from {processing_target} volumes, which were " + f"averaged and smoothed with a Gaussian kernel (FWHM={smooth_kernel} mm). " + f"A single M0 estimate of {metadata['M0Estimate']} was used to produce a calibration " + "'image'. " + ) + else: + ref_str = ( + f"The reference volume was generated from {processing_target} volumes, which were " + f"averaged and smoothed with a Gaussian kernel (FWHM={smooth_kernel} mm). " + f"As no calibration images or provided M0 estimate was available for the ASL scan, " + "the reference volume was retained for later use in CBF calculation. " + ) + + workflow.__desc__ += ref_str + inputnode = pe.Node( niu.IdentityInterface( fields=[ diff --git a/aslprep/workflows/asl/gecbf.py b/aslprep/workflows/asl/gecbf.py index c20871590..1d9d74cea 100644 --- a/aslprep/workflows/asl/gecbf.py +++ b/aslprep/workflows/asl/gecbf.py @@ -163,7 +163,7 @@ def init_asl_gepreproc_wf(asl_file): m0_scale = config.workflow.m0_scale scorescrub = config.workflow.scorescrub basil = config.workflow.basil - smoothkernel = config.workflow.smooth_kernel + smooth_kernel = config.workflow.smooth_kernel if scorescrub: config.loggers.workflow.warning(f"SCORE/SCRUB processing will be disabled for {asl_file}") @@ -270,7 +270,7 @@ def init_asl_gepreproc_wf(asl_file): asl_reference_wf = init_asl_reference_ge_wf( metadata=metadata, aslcontext=run_data["aslcontext"], - smooth_kernel=smoothkernel, + smooth_kernel=smooth_kernel, name="asl_reference_ge_wf", ) diff --git a/aslprep/workflows/asl/hmc.py b/aslprep/workflows/asl/hmc.py index 9338e33e4..d2ce64bcd 100644 --- a/aslprep/workflows/asl/hmc.py +++ b/aslprep/workflows/asl/hmc.py @@ -37,7 +37,7 @@ def init_asl_hmc_wf( from aslprep.workflows.asl.hmc import init_asl_hmc_wf wf = init_asl_hmc_wf( - processing_target="controllabel", + processing_target="control", m0type="Separate", mem_gb=3, omp_nthreads=1, @@ -46,7 +46,7 @@ def init_asl_hmc_wf( Parameters ---------- - processing_target : {"controllabel", "deltam", "cbf"} + processing_target : {"control", "deltam", "cbf"} m0type : {"Separate", "Included", "Absent", "Estimate"} mem_gb : :obj:`float` Size of ASL file in GB @@ -88,7 +88,7 @@ def init_asl_hmc_wf( workflow = Workflow(name=name) separation_substr = "" - if processing_target == "controllabel" or m0type == "Included": + if processing_target == "control" or m0type == "Included": separation_substr = ( "Motion correction was performed separately for each of the volume types " "in order to account for intensity differences between different contrasts, " @@ -139,7 +139,7 @@ def init_asl_hmc_wf( if m0type == "Included": files_to_mcflirt.append("m0scan") - if processing_target == "controllabel": + if processing_target == "control": files_to_mcflirt += ["control", "label"] else: files_to_mcflirt.append(processing_target) diff --git a/aslprep/workflows/asl/util.py b/aslprep/workflows/asl/util.py index 651a4b105..72591b4a4 100644 --- a/aslprep/workflows/asl/util.py +++ b/aslprep/workflows/asl/util.py @@ -1,6 +1,7 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """Utility workflows.""" +import pandas as pd from nipype.interfaces import afni, fsl from nipype.interfaces import utility as niu from nipype.pipeline import engine as pe @@ -15,6 +16,7 @@ from aslprep.interfaces.niworkflows import EstimateReferenceImage from aslprep.interfaces.utility import SplitReferenceTarget +from aslprep.utils.asl import select_processing_target DEFAULT_MEMORY_MIN_GB = 0.01 @@ -215,7 +217,7 @@ def init_validate_asl_wf(asl_file=None, name="validate_asl_wf"): """ workflow = Workflow(name=name) workflow.__desc__ = """\ -First, the middle volume of the ASL timeseries was selected as the refernce volume and +First, the middle volume of the ASL timeseries was selected as the reference volume and brain extracted using *Nipype*'s custom brain extraction workflow. """ @@ -269,6 +271,7 @@ def init_validate_asl_wf(asl_file=None, name="validate_asl_wf"): def init_asl_reference_wf( asl_file=None, + aslcontext=None, sbref_files=None, pre_mask=False, name="asl_reference_wf", @@ -344,9 +347,19 @@ def init_asl_reference_wf( """ workflow = Workflow(name=name) - workflow.__desc__ = """\ -First, the middle volume of the ASL timeseries was selected as the refernce volume and -brain extracted using *Nipype*'s custom brain extraction workflow. + + reference_target = "" + if aslcontext is not None: + reference_target = select_processing_target(aslcontext) + aslcontext_df = pd.read_table(aslcontext) + if "m0scan" in aslcontext_df["volume_type"].values: + reference_target = "M0" + + reference_target += " " + + workflow.__desc__ = f"""\ +First, the middle {reference_target}volume of the ASL timeseries was selected as the +reference volume and brain extracted using *Nipype*'s custom brain extraction workflow. """ inputnode = pe.Node( diff --git a/docs/workflows.rst b/docs/workflows.rst index f165dc3de..da6f8c46a 100644 --- a/docs/workflows.rst +++ b/docs/workflows.rst @@ -167,7 +167,7 @@ Head-motion estimation from aslprep.workflows.asl.hmc import init_asl_hmc_wf wf = init_asl_hmc_wf( - processing_target="controllabel", + processing_target="control", m0type="Included", mem_gb=1, omp_nthreads=1, @@ -277,7 +277,7 @@ CBF Computation in native space wf = init_compute_cbf_wf( name_source=str(nii_file), - processing_target="controllabel", + processing_target="control", scorescrub=False, basil=False, metadata=metadata,