Skip to content

Commit c0a25f9

Browse files
authored
Merge pull request #102 from mgxd/enh/cifti-rework
ENH: Incorporate subcortical CIFTI alignment to functional processing
2 parents 7321ef8 + 3bec948 commit c0a25f9

8 files changed

+282
-81
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
ACCUMBENS_LEFT
2+
26 255 165 0 255
3+
ACCUMBENS_RIGHT
4+
58 255 165 0 255
5+
AMYGDALA_LEFT
6+
18 103 255 255 255
7+
AMYGDALA_RIGHT
8+
54 103 255 255 255
9+
BRAIN_STEM
10+
16 119 159 176 255
11+
CAUDATE_LEFT
12+
11 122 186 220 255
13+
CAUDATE_RIGHT
14+
50 122 186 220 255
15+
CEREBELLUM_LEFT
16+
8 230 148 34 255
17+
CEREBELLUM_RIGHT
18+
47 230 148 34 255
19+
DIENCEPHALON_VENTRAL_LEFT
20+
28 165 42 42 255
21+
DIENCEPHALON_VENTRAL_RIGHT
22+
60 165 42 42 255
23+
HIPPOCAMPUS_LEFT
24+
17 220 216 20 255
25+
HIPPOCAMPUS_RIGHT
26+
53 220 216 20 255
27+
PALLIDUM_LEFT
28+
13 12 48 255 255
29+
PALLIDUM_RIGHT
30+
52 13 48 255 255
31+
PUTAMEN_LEFT
32+
12 236 13 176 255
33+
PUTAMEN_RIGHT
34+
51 236 13 176 255
35+
THALAMUS_LEFT
36+
10 0 118 14 255
37+
THALAMUS_RIGHT
38+
49 0 118 14 255
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
1.330660649 -0.004427136451 0.01025602509 -41.20370054
2+
-0.003753457765 1.339140797 0.153721562 -68.37553187
3+
-0.01546533827 -0.05324436952 1.426601839 -22.76900701
4+
0 0 0 1
Binary file not shown.

nibabies/interfaces/workbench.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,8 +1377,6 @@ class VolumeLabelImportInputSpec(CommandLineInputSpec):
13771377
desc="set any voxels with values not mentioned in the label list to the ??? label",
13781378
)
13791379
unlabeled_values = traits.Int(
1380-
0,
1381-
usedefault=True,
13821380
argstr="-unlabeled-value %d",
13831381
desc="the value that will be interpreted as unlabeled",
13841382
)
@@ -1433,7 +1431,7 @@ class VolumeLabelImport(WBCommand):
14331431
>>> label_import.inputs.label_list_file = data_dir / 'label_list.txt'
14341432
>>> label_import.cmdline #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
14351433
'wb_command -volume-label-import .../atlas.nii .../label_list.txt \
1436-
atlas_labels.nii.gz -unlabeled-value 0'
1434+
atlas_labels.nii.gz'
14371435
"""
14381436

14391437
input_spec = VolumeLabelImportInputSpec

nibabies/workflows/bold/alignment.py

Lines changed: 136 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,86 @@
22
Subcortical alignment into MNI space
33
"""
44

5-
from nibabies.interfaces.nibabel import MergeROIs
5+
from nipype.interfaces import utility as niu, fsl
6+
from nipype.pipeline import engine as pe
7+
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
8+
9+
from ...interfaces.workbench import VolumeLabelImport
10+
11+
from pkg_resources import resource_filename
12+
13+
14+
def init_subcortical_rois_wf(*, name="subcortical_rois_wf"):
15+
"""
16+
Refine segmentations into volumes of expected CIFTI subcortical structures.
17+
18+
19+
Parameters
20+
----------
21+
name : :obj:`str`
22+
Name of the workflow
23+
24+
Inputs
25+
------
26+
MNIInfant_aseg : :obj:`str`
27+
FreeSurfer's aseg in MNIInfant space
28+
29+
Outputs
30+
-------
31+
MNIInfant_rois : :obj:`str`
32+
Subcortical ROIs in MNIInfant space
33+
MNI152_rois : :obj:`str`
34+
Subcortical ROIs in `MNI152NLin6Asym` space
35+
"""
36+
from templateflow.api import get as get_template
37+
38+
# TODO: Implement BOLD refinement once InfantFS outputs subj/mri/wmparc.mgz
39+
# The code is found at
40+
# https://github.com/DCAN-Labs/dcan-infant-pipeline/blob/
41+
# 0e9c2fe32fb4a5032d0a2a3e0905ad97fa52b398/PostFreeSurfer/scripts/
42+
# FreeSurfer2CaretConvertAndRegisterNonlinear.sh
43+
# Lines 70-78 & 116-127
44+
# #
45+
# For now, just use the aseg
46+
47+
workflow = Workflow(name=name)
48+
inputnode = pe.Node(niu.IdentityInterface(fields=["MNIInfant_aseg"]), name='inputnode')
49+
outputnode = pe.Node(
50+
niu.IdentityInterface(fields=["MNIInfant_rois", "MNI152_rois"]),
51+
name='outputnode',
52+
)
53+
# Fetch the HCP volumetric template
54+
tpl_rois = get_template(
55+
'MNI152NLin6Asym', resolution=2, atlas="HCP", suffix="dseg", raise_empty=True
56+
)
57+
outputnode.inputs.MNI152_rois = tpl_rois
58+
59+
# This will only used for the wmparc in subject space
60+
# For now, define it and don't run it
61+
# TODO: Move to TemplateFlow
62+
63+
# tpl_avgwmparc = resource_filename(
64+
# 'nibabies', 'data/tpl-MNI152NLin6Asym_res-01_desc-avgwmparc_dseg.nii.gz'
65+
# )
66+
# applywarp_tpl = pe.Node(
67+
# fsl.ApplyWarp(in_file=tpl_avgwmparc, ref_file=tpl_rois, interp="nn"),
68+
# name="applywarp_std"
69+
# )
70+
71+
subcortical_labels = resource_filename(
72+
'nibabies', 'data/FreeSurferSubcorticalLabelTableLut.txt'
73+
)
74+
refine_bold_rois = pe.Node(
75+
VolumeLabelImport(label_list_file=subcortical_labels, discard_others=True),
76+
name="refine_bold_rois",
77+
)
78+
79+
workflow.connect([
80+
(inputnode, refine_bold_rois, [("MNIInfant_aseg", "in_file")]),
81+
# (applywarp_tpl, refine_std_rois, [("out_file", "in_file")]),
82+
(refine_bold_rois, outputnode, [("out_file", "MNIInfant_rois")]),
83+
])
84+
return workflow
685

786

887
def init_subcortical_mni_alignment_wf(*, vol_sigma=0.8, name='subcortical_mni_alignment_wf'):
@@ -23,22 +102,22 @@ def init_subcortical_mni_alignment_wf(*, vol_sigma=0.8, name='subcortical_mni_al
23102
24103
Inputs
25104
------
26-
bold_file : :obj:`str`
27-
BOLD file
28-
bold_roi : :obj:`str`
29-
File containing ROIs in BOLD space
30-
atlas_roi : :obj:`str`
31-
File containing ROIs in atlas space
32-
std_xfm : :obj:`str`
33-
File containing transform to the standard (MNI) space
105+
MNIInfant_bold : :obj:`str`
106+
BOLD file in MNI Infant space
107+
MNIInfant_rois : :obj:`str`
108+
File containing ROIs in MNI Infant space
109+
MNI152_rois : :obj:`str`
110+
File containing ROIs in MNI152NLin6Asym space
34111
35112
Outputs
36113
-------
37-
subcortical_file : :obj:`str`
114+
subcortical_volume : :obj:`str`
38115
Volume file containing all ROIs individually aligned to standard
116+
subcortical_labels : :obj:`str`
117+
Volume file containing all labels
39118
"""
40-
from nipype.pipeline import engine as pe
41-
from nipype.interfaces import utility as niu, fsl
119+
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
120+
from ...interfaces.nibabel import MergeROIs
42121
from ...interfaces.workbench import (
43122
CiftiCreateDenseTimeseries,
44123
CiftiCreateLabel,
@@ -49,19 +128,21 @@ def init_subcortical_mni_alignment_wf(*, vol_sigma=0.8, name='subcortical_mni_al
49128
VolumeAffineResample,
50129
VolumeAllLabelsToROIs,
51130
VolumeLabelExportTable,
52-
VolumeLabelImport,
53131
)
54-
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
55-
132+
# reuse saved atlas to atlas transform
133+
atlas_xfm = resource_filename("nibabies", "data/MNIInfant_to_MNI1526NLinAsym.mat")
56134
inputnode = pe.Node(
57-
niu.IdentityInterface(fields=["bold_file", "bold_roi", "atlas_roi", "atlas_xfm"]),
135+
niu.IdentityInterface(fields=["MNIInfant_bold", "MNIInfant_rois", "MNI152_rois"]),
58136
name="inputnode",
59137
)
60-
outputnode = pe.Node(niu.IdentityInterface(fields=["subcortical_file"]), name='outputnode')
138+
outputnode = pe.Node(
139+
niu.IdentityInterface(fields=["subcortical_volume", "subcortical_labels"]),
140+
name='outputnode',
141+
)
61142

62-
applyxfm_atlas = pe.Node(fsl.ApplyXFM(), name="applyxfm_atlas")
143+
applyxfm_atlas = pe.Node(fsl.ApplyXFM(in_matrix_file=atlas_xfm), name="applyxfm_atlas")
63144
vol_resample = pe.Node(
64-
VolumeAffineResample(method="ENCLOSING_VOXEL", flirt=True),
145+
VolumeAffineResample(method="ENCLOSING_VOXEL", flirt=True, affine=atlas_xfm),
65146
name="vol_resample"
66147
)
67148
subj_rois = pe.Node(VolumeAllLabelsToROIs(label_map=1), name="subj_rois")
@@ -167,26 +248,24 @@ def init_subcortical_mni_alignment_wf(*, vol_sigma=0.8, name='subcortical_mni_al
167248
# fmt: off
168249
workflow.connect([
169250
(inputnode, applyxfm_atlas, [
170-
("bold_file", "in_file"),
171-
("atlas_roi", "reference"),
172-
("atlas_xfm", "in_matrix_file")]),
251+
("MNIInfant_bold", "in_file"),
252+
("MNI152_rois", "reference")]),
173253
(inputnode, vol_resample, [
174-
("bold_roi", "in_file"),
175-
("atlas_xfm", "affine"),
176-
("bold_roi", "flirt_source_volume")]),
254+
("MNIInfant_rois", "in_file"),
255+
("MNIInfant_rois", "flirt_source_volume")]),
177256
(applyxfm_atlas, vol_resample, [
178257
("out_file", "volume_space"),
179258
("out_file", "flirt_target_volume")]),
180-
(inputnode, subj_rois, [("bold_roi", "in_file")]),
181-
(inputnode, atlas_rois, [("atlas_roi", "in_file")]),
259+
(inputnode, subj_rois, [("MNIInfant_rois", "in_file")]),
260+
(inputnode, atlas_rois, [("MNI152_rois", "in_file")]),
182261
(subj_rois, split_rois, [("out_file", "in_file")]),
183262
(atlas_rois, split_atlas_rois, [("out_file", "in_file")]),
184-
(inputnode, atlas_labels, [("atlas_roi", "in_file")]),
263+
(inputnode, atlas_labels, [("MNI152_rois", "in_file")]),
185264
(atlas_labels, parse_labels, [("out_file", "label_file")]),
186265
# for loop across ROIs
187266
(split_rois, roi2atlas, [("out_files", "in_file")]),
188267
(split_atlas_rois, roi2atlas, [("out_files", "reference")]),
189-
(inputnode, applyxfm_roi, [("bold_file", "in_file")]),
268+
(inputnode, applyxfm_roi, [("MNIInfant_bold", "in_file")]),
190269
(split_atlas_rois, applyxfm_roi, [("out_files", "reference")]),
191270
(roi2atlas, applyxfm_roi, [("out_matrix_file", "in_matrix_file")]),
192271
(applyxfm_roi, bold_mask_roi, [("out_file", "in_file")]),
@@ -216,7 +295,8 @@ def init_subcortical_mni_alignment_wf(*, vol_sigma=0.8, name='subcortical_mni_al
216295
("op_files", "operand_files"),
217296
("op_string", "op_string")]),
218297
(separate, merge_rois, [("volume_all_file", "in_files")]),
219-
(merge_rois, outputnode, [("out_file", "subcortical_file")]),
298+
(merge_rois, outputnode, [("out_file", "subcortical_volume")]),
299+
(inputnode, outputnode, [("MNI152_rois", "subcortical_labels")]),
220300
])
221301
# fmt: on
222302
return workflow
@@ -267,3 +347,29 @@ def format_agg_rois(rois):
267347
268348
"""
269349
return rois[0], rois[1:], ("-add %s " * (len(rois) - 1)).strip()
350+
351+
352+
def drop_labels(in_file):
353+
"""Drop non-subcortical labels"""
354+
from pathlib import Path
355+
import nibabel as nb
356+
import numpy as np
357+
from niworkflows.interfaces.cifti import _reorient_image
358+
359+
# FreeSurfer LUT values
360+
expected_labels = {
361+
8, 10, 11, 12, 13, 16, 17, 18, 26, 28, 47, 49, 50, 51, 52, 53, 54, 58, 60,
362+
}
363+
img = _reorient_image(nb.load(in_file), orientation="LAS")
364+
hdr = img.header
365+
data = np.asanyarray(img.dataobj).astype("int16")
366+
hdr.set_data_dtype("int16")
367+
labels = np.unique(data)
368+
369+
for label in labels:
370+
if label not in expected_labels:
371+
data[data == label] = 0
372+
373+
out_file = str(Path("ROIs.nii.gz").absolute())
374+
img.__class__(data, img.affine, header=hdr).to_filename(out_file)
375+
return out_file

nibabies/workflows/bold/base.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from niworkflows.utils.connections import pop_file, listify
2121
from niworkflows.interfaces.header import ValidateImage
22+
from niworkflows.interfaces.utility import KeySelect
2223
from sdcflows.interfaces.brainmask import BrainExtraction
2324

2425
from ...interfaces import DerivativesDataSink
@@ -765,22 +766,53 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False):
765766

766767
# CIFTI output
767768
if config.workflow.cifti_output:
769+
from .alignment import init_subcortical_rois_wf, init_subcortical_mni_alignment_wf
768770
from .resampling import init_bold_grayords_wf
771+
772+
key = None
773+
for space in spaces.get_spaces(nonstandard=False, dim=(3,)):
774+
if "MNIInfant" in space:
775+
key = space.replace(":", "_")
776+
777+
assert key is not None, f"MNIInfant not found in SpatialReferences: {spaces}"
778+
779+
# BOLD/ROIs should be in MNIInfant space
780+
cifti_select_std = pe.Node(
781+
KeySelect(fields=['bold_std', 'bold_aseg_std'], key=key),
782+
name='cifti_select_std',
783+
run_without_submitting=True,
784+
)
785+
786+
subcortical_rois_wf = init_subcortical_rois_wf()
787+
subcortical_mni_alignment_wf = init_subcortical_mni_alignment_wf()
769788
bold_grayords_wf = init_bold_grayords_wf(
770789
grayord_density=config.workflow.cifti_output,
771790
mem_gb=mem_gb['resampled'],
772-
repetition_time=metadata['RepetitionTime'])
791+
repetition_time=metadata['RepetitionTime']
792+
)
773793

774794
workflow.connect([
775-
(inputnode, bold_grayords_wf, [
776-
('subjects_dir', 'inputnode.subjects_dir')]),
777-
(bold_std_trans_wf, bold_grayords_wf, [
778-
('outputnode.bold_std', 'inputnode.bold_std'),
779-
('outputnode.spatial_reference', 'inputnode.spatial_reference')]),
795+
(bold_std_trans_wf, cifti_select_std, [
796+
("outputnode.bold_std", "bold_std"),
797+
("outputnode.bold_aseg_std", "bold_aseg_std"),
798+
("outputnode.spatial_reference", "keys")]),
799+
(cifti_select_std, subcortical_rois_wf, [
800+
("bold_aseg_std", "inputnode.MNIInfant_aseg")]),
801+
(cifti_select_std, subcortical_mni_alignment_wf, [
802+
("bold_std", "inputnode.MNIInfant_bold")]),
803+
# (bold_t1_trans_wf, subcortical_rois_wf, [
804+
# ("outputnode.bold_aseg_t1", "inputnode.bold_aseg")]),
805+
# (bold_bold_trans_wf, subcortical_mni_alignment_wf, [
806+
# ("outputnode.bold", "inputnode.bold_file")]),
807+
(subcortical_rois_wf, subcortical_mni_alignment_wf, [
808+
("outputnode.MNIInfant_rois", "inputnode.MNIInfant_rois"),
809+
("outputnode.MNI152_rois", "inputnode.MNI152_rois")]),
810+
(subcortical_mni_alignment_wf, bold_grayords_wf, [
811+
("outputnode.subcortical_volume", "inputnode.subcortical_volume"),
812+
("outputnode.subcortical_labels", "inputnode.subcortical_labels")]),
780813
(bold_surf_wf, bold_grayords_wf, [
781814
('outputnode.surfaces', 'inputnode.surf_files'),
782-
('outputnode.target', 'inputnode.surf_refs'),
783-
]),
815+
('outputnode.target', 'inputnode.surf_refs')]),
784816
(bold_grayords_wf, outputnode, [
785817
('outputnode.cifti_bold', 'bold_cifti'),
786818
('outputnode.cifti_variant', 'cifti_variant'),
@@ -867,7 +899,6 @@ def init_func_preproc_wf(bold_file, has_fieldmap=False):
867899
from niworkflows.interfaces.reportlets.registration import (
868900
SimpleBeforeAfterRPT as SimpleBeforeAfter,
869901
)
870-
from niworkflows.interfaces.utility import KeySelect
871902
from sdcflows.workflows.apply.registration import init_coeff2epi_wf
872903
from sdcflows.workflows.apply.correction import init_unwarp_wf
873904

0 commit comments

Comments
 (0)