Skip to content

Commit 0438cb8

Browse files
authored
Calculate basic CBF in whole FOV (#600)
1 parent 1609108 commit 0438cb8

File tree

4 files changed

+17
-81
lines changed

4 files changed

+17
-81
lines changed

aslprep/interfaces/cbf.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ class _ExtractCBFInputSpec(BaseInterfaceInputSpec):
9595
mandatory=True,
9696
desc="metadata for M0 scan. Only defined if M0Type is 'Separate'.",
9797
)
98-
in_mask = File(exists=True, mandatory=True, desc='mask')
9998
fwhm = traits.Float(default_value=5, usedefault=True, mandatory=False, desc='fwhm')
10099

101100

@@ -129,12 +128,12 @@ def _run_interface(self, runtime):
129128
aslcontext = pd.read_table(self.inputs.aslcontext)
130129
metadata = self.inputs.metadata.copy()
131130

132-
mask_data = nb.load(self.inputs.in_mask).get_fdata()
133-
134131
# read the preprocessed ASL data
135132
asl_img = nb.load(self.inputs.asl_file)
136133
asl_data = asl_img.get_fdata()
137134

135+
dims = asl_data.shape[:3]
136+
138137
if aslcontext.shape[0] != asl_img.shape[3]:
139138
raise ValueError(
140139
f'Number of rows in aslcontext ({aslcontext.shape[0]}) != '
@@ -152,11 +151,9 @@ def _run_interface(self, runtime):
152151
# extract m0 file and register it to ASL if separate
153152
if metadata['M0Type'] == 'Separate':
154153
m0file = self.inputs.m0scan
155-
m0data_smooth = smooth_image(nb.load(m0file), fwhm=self.inputs.fwhm).get_fdata()
156-
if len(m0data_smooth.shape) > 3:
157-
m0data = mask_data * np.mean(m0data_smooth, axis=3)
158-
else:
159-
m0data = mask_data * m0data_smooth
154+
m0data = smooth_image(nb.load(m0file), fwhm=self.inputs.fwhm).get_fdata()
155+
if len(m0data.shape) > 3:
156+
m0data = np.mean(m0data, axis=3)
160157

161158
m0tr = self.inputs.m0scan_metadata['RepetitionTimePreparation']
162159
if np.array(m0tr).size > 1 and np.std(m0tr) > 0:
@@ -165,8 +162,8 @@ def _run_interface(self, runtime):
165162
elif metadata['M0Type'] == 'Included':
166163
m0data = asl_data[:, :, :, m0_volume_idx]
167164
m0img = nb.Nifti1Image(m0data, asl_img.affine, asl_img.header)
168-
m0data_smooth = smooth_image(m0img, fwhm=self.inputs.fwhm).get_fdata()
169-
m0data = mask_data * np.mean(m0data_smooth, axis=3)
165+
m0data = smooth_image(m0img, fwhm=self.inputs.fwhm).get_fdata()
166+
m0data = np.mean(m0data, axis=3)
170167

171168
if np.array(metadata['RepetitionTimePreparation']).size > 1:
172169
m0tr = np.array(metadata['RepetitionTimePreparation'])[m0_volume_idx]
@@ -177,7 +174,7 @@ def _run_interface(self, runtime):
177174
raise ValueError('M0 scans have variable TR. ASLPrep does not support this.')
178175

179176
elif metadata['M0Type'] == 'Estimate':
180-
m0data = metadata['M0Estimate'] * mask_data
177+
m0data = np.full(dims, metadata['M0Estimate'])
181178

182179
m0tr = None
183180

@@ -194,7 +191,7 @@ def _run_interface(self, runtime):
194191
control_data = asl_data[:, :, :, control_volume_idx]
195192
control_img = nb.Nifti1Image(control_data, asl_img.affine, asl_img.header)
196193
control_img = smooth_image(control_img, fwhm=self.inputs.fwhm).get_fdata()
197-
m0data = mask_data * np.mean(control_img, axis=3)
194+
m0data = np.mean(control_img, axis=3)
198195

199196
# Use the control volumes' TR as the M0 TR.
200197
if np.array(metadata['RepetitionTimePreparation']).size > 1:
@@ -204,7 +201,7 @@ def _run_interface(self, runtime):
204201

205202
elif cbf_volume_idx:
206203
# If we have precalculated CBF data, we don't need M0, so we'll just use the mask.
207-
m0data = mask_data
204+
m0data = np.ones(dims)
208205

209206
m0tr = None
210207

@@ -314,7 +311,6 @@ class _ComputeCBFInputSpec(BaseInterfaceInputSpec):
314311
)
315312
m0_file = File(exists=True, mandatory=True, desc='M0 nifti file')
316313
m0tr = traits.Float(mandatory=False, desc='M0 TR, in seconds.')
317-
mask = File(exists=True, mandatory=True, desc='Mask nifti file')
318314
cbf_only = traits.Bool(
319315
mandatory=True,
320316
desc='Whether data are deltam (False) or CBF (True).',
@@ -392,9 +388,12 @@ def _run_interface(self, runtime):
392388
metadata = self.inputs.metadata
393389
m0_file = self.inputs.m0_file
394390
m0_scale = self.inputs.m0_scale
395-
mask_file = self.inputs.mask
396391
deltam_file = self.inputs.deltam # control - label signal intensities
397392

393+
deltam_img = nb.load(deltam_file)
394+
dims = deltam_img.shape[:3]
395+
mask_img = nb.Nifti1Image(np.ones(dims), deltam_img.affine, deltam_img.header)
396+
398397
if self.inputs.cbf_only:
399398
config.loggers.interface.debug('CBF data detected. Skipping CBF estimation.')
400399
self._results['cbf_ts'] = fname_presuffix(
@@ -434,7 +433,7 @@ def _run_interface(self, runtime):
434433

435434
# NOTE: Nilearn will still add a singleton time dimension for 3D imgs with
436435
# NiftiMasker.transform, until 0.12.0, so the arrays will currently be 2D no matter what.
437-
masker = maskers.NiftiMasker(mask_img=mask_file)
436+
masker = maskers.NiftiMasker(mask_img=mask_img)
438437
deltam_arr = masker.fit_transform(deltam_file).T # Transpose to SxT
439438
if deltam_arr.ndim != 2:
440439
raise ValueError(f'deltam is {deltam_arr.ndim}')

aslprep/tests/test_interfaces_cbf.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def test_computecbf_casl(datasets, tmp_path_factory):
2727
asl_file = _save_img(asl_data, tmpdir, 'asl.nii.gz')
2828
asl_mask = np.zeros((30, 30, 30), dtype=np.uint8)
2929
asl_mask[10:20, 10:20, 10:20] = 1
30-
mask_file = _save_img(asl_mask, tmpdir, 'mask.nii.gz')
3130
m0_file = _save_img(asl_mask, tmpdir, 'm0.nii.gz')
3231

3332
single_pld = 1.5
@@ -67,7 +66,6 @@ def test_computecbf_casl(datasets, tmp_path_factory):
6766
metadata=metadata,
6867
m0_scale=1,
6968
m0_file=m0_file,
70-
mask=mask_file,
7169
)
7270
results = interface.run(cwd=tmpdir)
7371
assert os.path.isfile(results.outputs.cbf_ts)
@@ -93,7 +91,6 @@ def test_computecbf_casl(datasets, tmp_path_factory):
9391
metadata=metadata,
9492
m0_scale=1,
9593
m0_file=m0_file,
96-
mask=mask_file,
9794
)
9895
with pytest.raises(ValueError, match='Number of PostLabelingDelays'):
9996
results = interface.run(cwd=tmpdir)
@@ -113,7 +110,6 @@ def test_computecbf_casl(datasets, tmp_path_factory):
113110
metadata=metadata,
114111
m0_scale=1,
115112
m0_file=m0_file,
116-
mask=mask_file,
117113
)
118114
results = interface.run(cwd=tmpdir)
119115
assert results.outputs.cbf_ts is None
@@ -142,7 +138,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
142138
asl_file = _save_img(asl_data, tmpdir, 'asl.nii.gz')
143139
asl_mask = np.zeros((30, 30, 30), dtype=np.uint8)
144140
asl_mask[10:20, 10:20, 10:20] = 1
145-
mask_file = _save_img(asl_mask, tmpdir, 'mask.nii.gz')
146141
m0_file = _save_img(asl_mask, tmpdir, 'm0.nii.gz')
147142

148143
single_pld = 1.5
@@ -180,7 +175,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
180175
metadata=metadata,
181176
m0_scale=1,
182177
m0_file=m0_file,
183-
mask=mask_file,
184178
)
185179
with pytest.raises(ValueError, match='not supported in ASLPrep'):
186180
results = interface.run(cwd=tmpdir)
@@ -201,7 +195,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
201195
metadata=metadata,
202196
m0_scale=1,
203197
m0_file=m0_file,
204-
mask=mask_file,
205198
)
206199
results = interface.run(cwd=tmpdir)
207200
assert os.path.isfile(results.outputs.cbf_ts)
@@ -227,7 +220,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
227220
metadata=metadata,
228221
m0_scale=1,
229222
m0_file=m0_file,
230-
mask=mask_file,
231223
)
232224
with pytest.raises(ValueError, match=r'Unsupported BolusCutOffTechnique \(QUIPSS\)'):
233225
results = interface.run(cwd=tmpdir)
@@ -247,7 +239,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
247239
metadata=metadata,
248240
m0_scale=1,
249241
m0_file=m0_file,
250-
mask=mask_file,
251242
)
252243
with pytest.raises(ValueError, match=r'Unsupported BolusCutOffTechnique \(QUIPSS\)'):
253244
results = interface.run(cwd=tmpdir)
@@ -268,7 +259,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
268259
metadata=metadata,
269260
m0_scale=1,
270261
m0_file=m0_file,
271-
mask=mask_file,
272262
)
273263
results = interface.run(cwd=tmpdir)
274264
assert os.path.isfile(results.outputs.cbf_ts)
@@ -294,7 +284,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
294284
metadata=metadata,
295285
m0_scale=1,
296286
m0_file=m0_file,
297-
mask=mask_file,
298287
)
299288
results = interface.run(cwd=tmpdir)
300289
assert os.path.isfile(results.outputs.mean_cbf)
@@ -317,7 +306,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
317306
metadata=metadata,
318307
m0_scale=1,
319308
m0_file=m0_file,
320-
mask=mask_file,
321309
)
322310
results = interface.run(cwd=tmpdir)
323311
assert os.path.isfile(results.outputs.cbf_ts)
@@ -343,7 +331,6 @@ def test_computecbf_pasl(datasets, tmp_path_factory):
343331
metadata=metadata,
344332
m0_scale=1,
345333
m0_file=m0_file,
346-
mask=mask_file,
347334
)
348335
results = interface.run(cwd=tmpdir)
349336
assert os.path.isfile(results.outputs.mean_cbf)
@@ -370,7 +357,6 @@ def test_compare_slicetiming(datasets, tmp_path_factory):
370357
asl_file = _save_img(asl_data, tmpdir, 'asl.nii.gz')
371358
asl_mask = np.zeros((30, 30, 30), dtype=np.uint8)
372359
asl_mask[10:20, 10:20, 10:20] = 1
373-
mask_file = _save_img(asl_mask, tmpdir, 'mask.nii.gz')
374360
m0_file = _save_img(asl_mask, tmpdir, 'm0.nii.gz')
375361

376362
ACQ_DICTS = [
@@ -396,7 +382,6 @@ def test_compare_slicetiming(datasets, tmp_path_factory):
396382
metadata=metadata,
397383
m0_scale=1,
398384
m0_file=m0_file,
399-
mask=mask_file,
400385
)
401386
results = interface.run(cwd=tmpdir)
402387
cbf_data.append(nb.load(results.outputs.cbf_ts).get_fdata())

aslprep/workflows/asl/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,6 @@ def init_asl_wf(
400400
)
401401
workflow.connect([
402402
(inputnode, cbf_wf, [
403-
('t1w_mask', 'inputnode.t1w_mask'),
404403
('t1w_tpms', 'inputnode.t1w_tpms'),
405404
('m0scan_metadata', 'inputnode.m0scan_metadata'),
406405
]),

aslprep/workflows/asl/cbf.py

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
BASILCBF,
1919
ComputeCBF,
2020
ExtractCBF,
21-
RefineMask,
2221
ScoreAndScrubCBF,
2322
)
2423
from aslprep.interfaces.parcellation import NiftiParcellate
@@ -31,7 +30,6 @@
3130
pcasl_or_pasl,
3231
prepare_basil_kwargs,
3332
)
34-
from aslprep.workflows.asl.reference import init_synthstrip_aslref_wf
3533

3634

3735
def init_cbf_wf(
@@ -104,8 +102,6 @@ def init_cbf_wf(
104102
asl mask NIFTI file
105103
t1w_tpms
106104
t1w probability maps
107-
t1w_mask
108-
t1w mask Nifti
109105
aslref2anat_xfm
110106
asl to t1w transformation file
111107
@@ -226,7 +222,6 @@ def init_cbf_wf(
226222
'm0scan_metadata',
227223
'asl_mask',
228224
't1w_tpms',
229-
't1w_mask',
230225
'aslref2anat_xfm',
231226
],
232227
),
@@ -257,37 +252,6 @@ def init_cbf_wf(
257252
name='outputnode',
258253
)
259254

260-
warp_t1w_mask_to_asl = pe.Node(
261-
ApplyTransforms(
262-
dimension=3,
263-
float=True,
264-
interpolation='NearestNeighbor',
265-
invert_transform_flags=[True],
266-
input_image_type=3,
267-
args='-v',
268-
),
269-
name='warp_t1w_mask_to_asl',
270-
)
271-
workflow.connect([
272-
(inputnode, warp_t1w_mask_to_asl, [
273-
('asl_mask', 'reference_image'),
274-
('t1w_mask', 'input_image'),
275-
('aslref2anat_xfm', 'transforms'),
276-
]),
277-
]) # fmt:skip
278-
279-
reduce_mask = pe.Node(
280-
RefineMask(),
281-
mem_gb=0.2,
282-
run_without_submitting=True,
283-
name='reduce_mask',
284-
)
285-
286-
workflow.connect([
287-
(inputnode, reduce_mask, [('asl_mask', 'asl_mask')]),
288-
(warp_t1w_mask_to_asl, reduce_mask, [('output_image', 't1w_mask')]),
289-
]) # fmt:skip
290-
291255
# Warp tissue probability maps to ASL space
292256
def _pick_gm(files):
293257
if not isinstance(files, list):
@@ -383,7 +347,6 @@ def _getfiledir(file):
383347
('aslcontext', 'aslcontext'),
384348
('m0scan_metadata', 'm0scan_metadata'),
385349
]),
386-
(reduce_mask, extract_deltam, [('out_mask', 'in_mask')]),
387350
]) # fmt:skip
388351

389352
if metadata['M0Type'] == 'Separate':
@@ -393,15 +356,6 @@ def _getfiledir(file):
393356
(mean_m0, extract_deltam, [('out_file', 'm0scan')]),
394357
]) # fmt:skip
395358

396-
enhance_and_skullstrip_m0scan_wf = init_synthstrip_aslref_wf(
397-
disable_n4=config.workflow.disable_n4,
398-
name='enhance_and_skullstrip_m0scan_wf',
399-
)
400-
workflow.connect([
401-
(mean_m0, enhance_and_skullstrip_m0scan_wf, [('out_file', 'inputnode.in_file')]),
402-
(enhance_and_skullstrip_m0scan_wf, reduce_mask, [('outputnode.mask_file', 'm0_mask')]),
403-
]) # fmt:skip
404-
405359
compute_cbf = pe.Node(
406360
ComputeCBF(
407361
cbf_only=processing_target == 'cbf',
@@ -413,7 +367,6 @@ def _getfiledir(file):
413367
)
414368

415369
workflow.connect([
416-
(reduce_mask, compute_cbf, [('out_mask', 'mask')]),
417370
(extract_deltam, compute_cbf, [
418371
('out_file', 'deltam'),
419372
('m0_file', 'm0_file'),
@@ -447,7 +400,7 @@ def _getfiledir(file):
447400
[@dolui2017structural;@dolui2016scrub].
448401
"""
449402
workflow.connect([
450-
(reduce_mask, score_and_scrub_cbf, [('out_mask', 'mask')]),
403+
(inputnode, score_and_scrub_cbf, [('asl_mask', 'mask')]),
451404
(compute_cbf, score_and_scrub_cbf, [('cbf_ts', 'cbf_ts')]),
452405
(gm_tfm, score_and_scrub_cbf, [('output_image', 'gm_tpm')]),
453406
(wm_tfm, score_and_scrub_cbf, [('output_image', 'wm_tpm')]),
@@ -520,7 +473,7 @@ def _getfiledir(file):
520473
)
521474

522475
workflow.connect([
523-
(reduce_mask, basilcbf, [('out_mask', 'mask')]),
476+
(inputnode, basilcbf, [('asl_mask', 'mask')]),
524477
(extract_deltam, basilcbf, [
525478
('out_file', 'deltam'),
526479
('m0_file', 'mzero'),

0 commit comments

Comments
 (0)