Skip to content

Commit 0a59cd2

Browse files
authored
Merge pull request #300 from mgxd/fix/goodvoxels
FIX: "Goodvoxels" projection
2 parents 0133ad4 + da33f6b commit 0a59cd2

File tree

11 files changed

+623
-181
lines changed

11 files changed

+623
-181
lines changed

.circleci/bcp_anat_outputs.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,20 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T1w_mode-image_xf
2323
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-fsnative_mode-image_xfm.txt
2424
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-MNIInfant+1_mode-image_xfm.h5
2525
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_curv.shape.gii
26+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-reg_sphere.surf.gii
2627
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_inflated.surf.gii
2728
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_midthickness.surf.gii
2829
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_pial.surf.gii
30+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_desc-reg_sphere.surf.gii
2931
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_sulc.shape.gii
3032
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_thickness.shape.gii
3133
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_white.surf.gii
3234
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_curv.shape.gii
35+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-reg_sphere.surf.gii
3336
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_inflated.surf.gii
3437
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_midthickness.surf.gii
3538
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_pial.surf.gii
39+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_desc-reg_sphere.surf.gii
3640
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_sulc.shape.gii
3741
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_thickness.shape.gii
3842
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_white.surf.gii

.circleci/bcp_full_outputs.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,20 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T1w_mode-image_xf
2323
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-fsnative_mode-image_xfm.txt
2424
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-MNIInfant+1_mode-image_xfm.h5
2525
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_curv.shape.gii
26+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-reg_sphere.surf.gii
2627
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_inflated.surf.gii
2728
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_midthickness.surf.gii
2829
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_pial.surf.gii
30+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_desc-reg_sphere.surf.gii
2931
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_sulc.shape.gii
3032
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_thickness.shape.gii
3133
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_white.surf.gii
3234
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_curv.shape.gii
35+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-reg_sphere.surf.gii
3336
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_inflated.surf.gii
3437
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_midthickness.surf.gii
3538
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_pial.surf.gii
39+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_desc-reg_sphere.surf.gii
3640
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_sulc.shape.gii
3741
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_thickness.shape.gii
3842
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_white.surf.gii

nibabies/interfaces/utils.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os
2+
import re
3+
4+
from nipype.interfaces.base import (
5+
BaseInterfaceInputSpec,
6+
File,
7+
InputMultiObject,
8+
OutputMultiObject,
9+
SimpleInterface,
10+
TraitedSpec,
11+
traits,
12+
)
13+
14+
15+
class CiftiSelectInputSpec(BaseInterfaceInputSpec):
16+
hemi = traits.Enum("L", "R", desc="Hemisphere")
17+
surfaces = InputMultiObject(File(exists=True), desc="Surfaces")
18+
morphometrics = InputMultiObject(File(exists=True), desc="Surface morphometrics")
19+
spherical_registrations = InputMultiObject(
20+
File(exists=True), desc="Spherical registration to fsLR"
21+
)
22+
template_spheres = InputMultiObject(File(exists=True), desc="fsLR sphere")
23+
template_surfaces = InputMultiObject(File(exists=True), desc="fsLR midthickness")
24+
template_rois = InputMultiObject(File(exists=True), desc="fsLR ROIs")
25+
26+
27+
class CiftiSelectOutputSpec(TraitedSpec):
28+
white = OutputMultiObject(File, desc="white surface")
29+
pial = OutputMultiObject(File, desc="pial surface")
30+
midthickness = OutputMultiObject(File, desc="midthickness surface")
31+
thickness = OutputMultiObject(File, desc="thickness surface")
32+
sphere_reg = OutputMultiObject(File, desc="fsLR spherical regisration")
33+
template_sphere = OutputMultiObject(File, desc="fsLR sphere")
34+
template_surface = OutputMultiObject(File, desc="fsLR surface (midthickness)")
35+
template_roi = OutputMultiObject(File, desc="fsLR ROIs")
36+
37+
38+
class CiftiSelect(SimpleInterface):
39+
input_spec = CiftiSelectInputSpec
40+
output_spec = CiftiSelectOutputSpec
41+
42+
def _run_interface(self, runtime):
43+
idx = 0 if self.inputs.hemi == "L" else 1
44+
all_surfaces = (self.inputs.surfaces or []) + (self.inputs.morphometrics or [])
45+
container = {
46+
'white': [],
47+
'pial': [],
48+
'midthickness': [],
49+
'thickness': [],
50+
'sphere_reg': self.inputs.spherical_registrations or [],
51+
'template_sphere': self.inputs.template_spheres or [],
52+
'template_surface': self.inputs.template_surfaces or [],
53+
'template_roi': self.inputs.template_rois or [],
54+
}
55+
find_name = re.compile(r'(?:^|[^d])(?P<name>white|pial|midthickness|thickness)')
56+
for surface in all_surfaces:
57+
match = find_name.search(os.path.basename(surface))
58+
if match:
59+
container[match.group('name')].append(surface)
60+
61+
for name, vals in container.items():
62+
if vals:
63+
self._results[name] = sorted(vals, key=os.path.basename)[idx]
64+
return runtime

nibabies/interfaces/workbench.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,3 +1570,113 @@ class CreateSignedDistanceVolume(WBCommand):
15701570
input_spec = CreateSignedDistanceVolumeInputSpec
15711571
output_spec = CreateSignedDistanceVolumeOutputSpec
15721572
_cmd = "wb_command -create-signed-distance-volume"
1573+
1574+
1575+
class SurfaceAverageInputSpec(CommandLineInputSpec):
1576+
out_file = File(
1577+
name_template="averaged.surf.gii",
1578+
position=0,
1579+
desc="output file",
1580+
)
1581+
surfaces = InputMultiObject(
1582+
traits.Either(
1583+
File(exists=True),
1584+
traits.Tuple(File(exists=True), traits.Float),
1585+
),
1586+
argstr="%s",
1587+
position=3,
1588+
desc="Surface, or surface and weighted average tuple, to include in the average",
1589+
)
1590+
1591+
1592+
class SurfaceAverageOutputSpec(TraitedSpec):
1593+
out_file = File(desc="The output averaged surface")
1594+
# stddev_metric = File(desc="The output metric for 3D sample standard deviation")
1595+
# uncert_metric = File(desc="The output metric for uncertainty")
1596+
1597+
1598+
class SurfaceAverage(WBCommand):
1599+
"""
1600+
AVERAGE SURFACE FILES TOGETHER
1601+
wb_command -surface-average
1602+
<surface-out> - output - the output averaged surface
1603+
1604+
[-stddev] - compute 3D sample standard deviation
1605+
<stddev-metric-out> - output - the output metric for 3D sample
1606+
standard deviation
1607+
1608+
[-uncertainty] - compute caret5 'uncertainty'
1609+
<uncert-metric-out> - output - the output metric for uncertainty
1610+
1611+
[-surf] - repeatable - specify a surface to include in the average
1612+
<surface> - a surface file to average
1613+
1614+
[-weight] - specify a weighted average
1615+
<weight> - the weight to use (default 1)
1616+
1617+
The 3D sample standard deviation is computed as
1618+
'sqrt(sum(squaredlength(xyz - mean(xyz)))/(n - 1))'.
1619+
1620+
Uncertainty is a legacy measure used in caret5, and is computed as
1621+
'sum(length(xyz - mean(xyz)))/n'.
1622+
1623+
When weights are used, the 3D sample standard deviation treats them as
1624+
reliability weights.
1625+
"""
1626+
1627+
input_spec = SurfaceAverageInputSpec
1628+
output_spec = SurfaceAverageOutputSpec
1629+
_cmd = "wb_command -surface-average"
1630+
1631+
def _format_arg(self, name, trait_spec, value):
1632+
if name == 'surfaces':
1633+
cmd = []
1634+
for val in value:
1635+
if len(val) == 2:
1636+
cmd.append(f"{val[0]} -weight {val[-1]}")
1637+
else:
1638+
cmd.append(val)
1639+
return '-surf ' + ' -surf '.join(cmd)
1640+
return super()._format_arg(name, trait_spec, value)
1641+
1642+
def _list_output(self):
1643+
outputs = self.output_spec().get()
1644+
outputs["out_file"] = os.path.abspath(self.inputs.out_file)
1645+
return outputs
1646+
1647+
1648+
class SurfaceVertexAreasInputSpec(CommandLineInputSpec):
1649+
in_file = File(
1650+
exists=True,
1651+
mandatory=True,
1652+
position=0,
1653+
argstr="%s",
1654+
desc="Input surface",
1655+
)
1656+
out_file = File(
1657+
name_template="%s.shape.gii",
1658+
name_source="in_file",
1659+
position=1,
1660+
argstr="%s",
1661+
desc="Output vertex areas",
1662+
)
1663+
1664+
1665+
class SurfaceVertexAreasOutputSpec(TraitedSpec):
1666+
out_file = File(desc="Output vertex areas")
1667+
1668+
1669+
class SurfaceVertexAreas(WBCommand):
1670+
"""
1671+
MEASURE SURFACE AREA EACH VERTEX IS RESPONSIBLE FOR
1672+
wb_command -surface-vertex-areas
1673+
<surface> - the surface to measure
1674+
<metric> - output - the output metric
1675+
1676+
Each vertex gets one third of the area of each triangle it is a part of.
1677+
Units are mm^2.
1678+
"""
1679+
1680+
input_spec = SurfaceVertexAreasInputSpec
1681+
output_spec = SurfaceVertexAreasOutputSpec
1682+
_cmd = "wb_command -surface-vertex-areas"

nibabies/tests/test_config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ def test_config_spaces():
100100

101101
assert [str(s) for s in spaces.get_standard(full_spec=True)] == [
102102
'MNIInfant:cohort-1:res-native', # Default output space
103-
'fsaverage:den-164k',
104103
'MNI152NLin6Asym:res-2',
105104
]
106105

nibabies/workflows/anatomical/base.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def init_infant_anat_wf(
165165
# registration sphere space is dependent on surface recon method
166166
"sphere_reg",
167167
"sphere_reg_fsLR",
168+
"midthickness_fsLR",
168169
]
169170
),
170171
name="outputnode",
@@ -402,7 +403,7 @@ def init_infant_anat_wf(
402403
elif config.workflow.surface_recon_method == 'mcribs':
403404
from nipype.interfaces.ants import DenoiseImage
404405

405-
from .surfaces import init_mcribs_surface_recon_wf
406+
from .surfaces import init_mcribs_sphere_reg_wf, init_mcribs_surface_recon_wf
406407

407408
# Denoise raw T2w, since using the template / preproc resulted in intersection errors
408409
denoise_raw_t2w = pe.Node(
@@ -511,20 +512,29 @@ def init_infant_anat_wf(
511512
# fmt: on
512513

513514
if cifti_output:
514-
from smriprep.workflows.surfaces import init_morph_grayords_wf
515+
from nibabies.workflows.anatomical.resampling import (
516+
init_anat_fsLR_resampling_wf,
517+
)
515518

516-
morph_grayords_wf = init_morph_grayords_wf(grayord_density=cifti_output)
519+
is_mcribs = config.workflow.surface_recon_method == "mcribs"
520+
# handles morph_grayords_wf
521+
anat_fsLR_resampling_wf = init_anat_fsLR_resampling_wf(cifti_output, mcribs=is_mcribs)
517522
anat_derivatives_wf.get_node('inputnode').inputs.cifti_density = cifti_output
518523
# fmt:off
519524
wf.connect([
520-
(surface_recon_wf, morph_grayords_wf, [
525+
(sphere_reg_wf, anat_fsLR_resampling_wf, [
526+
('outputnode.sphere_reg', 'inputnode.sphere_reg'),
527+
('outputnode.sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR')]),
528+
(surface_recon_wf, anat_fsLR_resampling_wf, [
521529
('outputnode.subject_id', 'inputnode.subject_id'),
522530
('outputnode.subjects_dir', 'inputnode.subjects_dir'),
523-
]),
524-
(morph_grayords_wf, anat_derivatives_wf, [
531+
('outputnode.surfaces', 'inputnode.surfaces'),
532+
('outputnode.morphometrics', 'inputnode.morphometrics')]),
533+
(anat_fsLR_resampling_wf, anat_derivatives_wf, [
525534
("outputnode.cifti_morph", "inputnode.cifti_morph"),
526-
("outputnode.cifti_metadata", "inputnode.cifti_metadata"),
527-
]),
535+
("outputnode.cifti_metadata", "inputnode.cifti_metadata")]),
536+
(anat_fsLR_resampling_wf, outputnode, [
537+
("outputnode.midthickness_fsLR", "midthickness_fsLR")])
528538
])
529539
# fmt:on
530540

nibabies/workflows/anatomical/outputs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,8 +752,8 @@ def init_anat_derivatives_wf(
752752
(inputnode, ds_regs, [('sphere_reg', 'in_file'),
753753
('source_files', 'source_file')]),
754754
(name_regs, ds_regs, [('hemi', 'hemi')]),
755-
(inputnode, name_reg_fsLR, [('sphere_reg', 'in_file')]),
756-
(inputnode, ds_reg_fsLR, [('sphere_reg', 'in_file'),
755+
(inputnode, name_reg_fsLR, [('sphere_reg_fsLR', 'in_file')]),
756+
(inputnode, ds_reg_fsLR, [('sphere_reg_fsLR', 'in_file'),
757757
('source_files', 'source_file')]),
758758
(name_reg_fsLR, ds_reg_fsLR, [('hemi', 'hemi')]),
759759
(inputnode, name_morphs, [('morphometrics', 'in_file')]),

0 commit comments

Comments
 (0)