Skip to content

Commit 8548ad3

Browse files
committed
ENH: Add workflow to normalize CSF prior to spatial normalization
1 parent 302614a commit 8548ad3

File tree

1 file changed

+37
-14
lines changed

1 file changed

+37
-14
lines changed

nibabies/workflows/anatomical/preproc.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,50 @@ def init_anat_preproc_wf(
6868
return wf
6969

7070

71-
def init_anat_csf_norm_wf(name='anat_csf_norm_wf') -> LiterateWorkflow:
71+
def init_csf_norm_wf(name: str = 'csf_norm_wf') -> LiterateWorkflow:
7272
"""Replace low intensity voxels within the CSF mask with the median value."""
7373

7474
workflow = LiterateWorkflow(name=name)
75-
inputnode = niu.IdentityInterface(fields=['anat_preproc', 'anat_dseg'], name='inputnode')
75+
workflow.__desc__ = (
76+
'The CSF mask was used to normalize the anatomical template by the median of voxels '
77+
'within the mask.'
78+
)
79+
inputnode = niu.IdentityInterface(fields=['anat_preproc', 'anat_tpms'], name='inputnode')
7680
outputnode = niu.IdentityInterface(fields=['anat_preproc'], name='outputnode')
7781

78-
applymask = pe.Node(ApplyMask(), name='applymask')
82+
# select CSF from BIDS-ordered list (GM, WM, CSF)
83+
select_csf = pe.Node(niu.Select(index=2), name='select_csf')
84+
norm_csf = pe.Node(niu.Function(function=_normalize_roi), name='norm_csf')
7985

80-
norm2median = pe.Node(niu.Function(function=_normalize_csf), name='norm2median')
81-
# 1. mask brain with CSF mask
82-
# fslmaths input.nii.gz -mas aseg_label-CSF_mask.nii.gz input_CSF.nii.gz
83-
# 2. get median intensity of nonzero voxels in mask
84-
# fslstats input_CSF.nii.gz -P 50
85-
# 3. normalize CSF-masked T2w to the median
86-
# fslmaths input_CSF.nii.gz -bin -mul <median intensity from (> median_CSF.nii.gz
87-
# 4. make the modified T2w, setting voxel intensity to be the max between the original T2w's,
88-
# and the normalized mask from (3)'s:
89-
# fslmaths input.nii.gz -max median_CSF.nii.gz input_floorCSF.nii.gz
86+
workflow.connect([
87+
(inputnode, select_csf, [('anat_tpms', 'in_list')]),
88+
(select_csf, norm_csf, [('out', 'mask_file')]),
89+
(inputnode, norm_csf, [('anat_preproc', 'in_file')]),
90+
(norm_csf, outputnode, [('out', 'anat_preproc')]),
91+
]) # fmt:skip
9092

9193
return workflow
9294

9395

94-
def _normalize_csf(in_file): ...
96+
def _normalize_roi(in_file, mask_file, threshold=0.2, out_file=None):
97+
"""Normalize low intensity voxels that fall within a given mask."""
98+
import nibabel as nb
99+
import numpy as np
100+
101+
img = nb.load(in_file)
102+
img_data = img.get_fdata()
103+
mask_img = nb.load(mask_file)
104+
# binary mask
105+
bin_mask = mask_img.get_fdata() > threshold
106+
mask_data = bin_mask * img_data
107+
108+
median = np.median(mask_data[mask_data > 0])
109+
normed_data = np.maximum(img_data, bin_mask * median)
110+
111+
oimg = img.__class__(normed_data, img.affine, img.header)
112+
if not out_file:
113+
from nipype.utils.filemanip import fname_presuffix
114+
115+
out_file = fname_presuffix(in_file, suffix='normed')
116+
oimg.to_filename(out_file)
117+
return out_file

0 commit comments

Comments
 (0)