Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: "Goodvoxels" projection #300

Merged
merged 14 commits into from
Jun 23, 2023
4 changes: 4 additions & 0 deletions .circleci/bcp_anat_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T1w_mode-image_xf
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-fsnative_mode-image_xfm.txt
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-MNIInfant+1_mode-image_xfm.h5
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_curv.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_inflated.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_midthickness.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_pial.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_sulc.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_thickness.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_white.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_curv.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_inflated.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_midthickness.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_pial.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_sulc.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_thickness.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_white.surf.gii
Expand Down
4 changes: 4 additions & 0 deletions .circleci/bcp_full_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T1w_mode-image_xf
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-fsnative_mode-image_xfm.txt
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-MNIInfant+1_mode-image_xfm.h5
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_curv.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_inflated.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_midthickness.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_pial.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_sulc.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_thickness.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_white.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_curv.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_inflated.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_midthickness.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_pial.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_desc-reg_sphere.surf.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_sulc.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_thickness.shape.gii
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_white.surf.gii
Expand Down
64 changes: 64 additions & 0 deletions nibabies/interfaces/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import re

from nipype.interfaces.base import (
BaseInterfaceInputSpec,
File,
InputMultiObject,
OutputMultiObject,
SimpleInterface,
TraitedSpec,
traits,
)


class CiftiSelectInputSpec(BaseInterfaceInputSpec):
hemi = traits.Enum("L", "R", desc="Hemisphere")
surfaces = InputMultiObject(File(exists=True), desc="Surfaces")
morphometrics = InputMultiObject(File(exists=True), desc="Surface morphometrics")
spherical_registrations = InputMultiObject(
File(exists=True), desc="Spherical registration to fsLR"
)
template_spheres = InputMultiObject(File(exists=True), desc="fsLR sphere")
template_surfaces = InputMultiObject(File(exists=True), desc="fsLR midthickness")
template_rois = InputMultiObject(File(exists=True), desc="fsLR ROIs")


class CiftiSelectOutputSpec(TraitedSpec):
white = OutputMultiObject(File, desc="white surface")
pial = OutputMultiObject(File, desc="pial surface")
midthickness = OutputMultiObject(File, desc="midthickness surface")
thickness = OutputMultiObject(File, desc="thickness surface")
sphere_reg = OutputMultiObject(File, desc="fsLR spherical regisration")
template_sphere = OutputMultiObject(File, desc="fsLR sphere")
template_surface = OutputMultiObject(File, desc="fsLR surface (midthickness)")
template_roi = OutputMultiObject(File, desc="fsLR ROIs")


class CiftiSelect(SimpleInterface):
input_spec = CiftiSelectInputSpec
output_spec = CiftiSelectOutputSpec

def _run_interface(self, runtime):
idx = 0 if self.inputs.hemi == "L" else 1
all_surfaces = (self.inputs.surfaces or []) + (self.inputs.morphometrics or [])
container = {
'white': [],
'pial': [],
'midthickness': [],
'thickness': [],
'sphere_reg': self.inputs.spherical_registrations or [],
'template_sphere': self.inputs.template_spheres or [],
'template_surface': self.inputs.template_surfaces or [],
'template_roi': self.inputs.template_rois or [],
}
find_name = re.compile(r'(?:^|[^d])(?P<name>white|pial|midthickness|thickness)')
for surface in all_surfaces:
match = find_name.search(os.path.basename(surface))
if match:
container[match.group('name')].append(surface)

for name, vals in container.items():
if vals:
self._results[name] = sorted(vals, key=os.path.basename)[idx]
return runtime
110 changes: 110 additions & 0 deletions nibabies/interfaces/workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -1570,3 +1570,113 @@ class CreateSignedDistanceVolume(WBCommand):
input_spec = CreateSignedDistanceVolumeInputSpec
output_spec = CreateSignedDistanceVolumeOutputSpec
_cmd = "wb_command -create-signed-distance-volume"


class SurfaceAverageInputSpec(CommandLineInputSpec):
out_file = File(
name_template="averaged.surf.gii",
position=0,
desc="output file",
)
surfaces = InputMultiObject(
traits.Either(
File(exists=True),
traits.Tuple(File(exists=True), traits.Float),
),
argstr="%s",
position=3,
desc="Surface, or surface and weighted average tuple, to include in the average",
)


class SurfaceAverageOutputSpec(TraitedSpec):
out_file = File(desc="The output averaged surface")
# stddev_metric = File(desc="The output metric for 3D sample standard deviation")
# uncert_metric = File(desc="The output metric for uncertainty")


class SurfaceAverage(WBCommand):
"""
AVERAGE SURFACE FILES TOGETHER
wb_command -surface-average
<surface-out> - output - the output averaged surface

[-stddev] - compute 3D sample standard deviation
<stddev-metric-out> - output - the output metric for 3D sample
standard deviation

[-uncertainty] - compute caret5 'uncertainty'
<uncert-metric-out> - output - the output metric for uncertainty

[-surf] - repeatable - specify a surface to include in the average
<surface> - a surface file to average

[-weight] - specify a weighted average
<weight> - the weight to use (default 1)

The 3D sample standard deviation is computed as
'sqrt(sum(squaredlength(xyz - mean(xyz)))/(n - 1))'.

Uncertainty is a legacy measure used in caret5, and is computed as
'sum(length(xyz - mean(xyz)))/n'.

When weights are used, the 3D sample standard deviation treats them as
reliability weights.
"""

input_spec = SurfaceAverageInputSpec
output_spec = SurfaceAverageOutputSpec
_cmd = "wb_command -surface-average"

def _format_arg(self, name, trait_spec, value):
if name == 'surfaces':
cmd = []
for val in value:
if len(val) == 2:
cmd.append(f"{val[0]} -weight {val[-1]}")
else:
cmd.append(val)
return '-surf ' + ' -surf '.join(cmd)
return super()._format_arg(name, trait_spec, value)

def _list_output(self):
outputs = self.output_spec().get()
outputs["out_file"] = os.path.abspath(self.inputs.out_file)
return outputs


class SurfaceVertexAreasInputSpec(CommandLineInputSpec):
in_file = File(
exists=True,
mandatory=True,
position=0,
argstr="%s",
desc="Input surface",
)
out_file = File(
name_template="%s.shape.gii",
name_source="in_file",
position=1,
argstr="%s",
desc="Output vertex areas",
)


class SurfaceVertexAreasOutputSpec(TraitedSpec):
out_file = File(desc="Output vertex areas")


class SurfaceVertexAreas(WBCommand):
"""
MEASURE SURFACE AREA EACH VERTEX IS RESPONSIBLE FOR
wb_command -surface-vertex-areas
<surface> - the surface to measure
<metric> - output - the output metric

Each vertex gets one third of the area of each triangle it is a part of.
Units are mm^2.
"""

input_spec = SurfaceVertexAreasInputSpec
output_spec = SurfaceVertexAreasOutputSpec
_cmd = "wb_command -surface-vertex-areas"
1 change: 0 additions & 1 deletion nibabies/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ def test_config_spaces():

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

Expand Down
26 changes: 18 additions & 8 deletions nibabies/workflows/anatomical/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def init_infant_anat_wf(
# registration sphere space is dependent on surface recon method
"sphere_reg",
"sphere_reg_fsLR",
"midthickness_fsLR",
]
),
name="outputnode",
Expand Down Expand Up @@ -402,7 +403,7 @@ def init_infant_anat_wf(
elif config.workflow.surface_recon_method == 'mcribs':
from nipype.interfaces.ants import DenoiseImage

from .surfaces import init_mcribs_surface_recon_wf
from .surfaces import init_mcribs_sphere_reg_wf, init_mcribs_surface_recon_wf

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

if cifti_output:
from smriprep.workflows.surfaces import init_morph_grayords_wf
from nibabies.workflows.anatomical.resampling import (
init_anat_fsLR_resampling_wf,
)

morph_grayords_wf = init_morph_grayords_wf(grayord_density=cifti_output)
is_mcribs = config.workflow.surface_recon_method == "mcribs"
# handles morph_grayords_wf
anat_fsLR_resampling_wf = init_anat_fsLR_resampling_wf(cifti_output, mcribs=is_mcribs)
anat_derivatives_wf.get_node('inputnode').inputs.cifti_density = cifti_output
# fmt:off
wf.connect([
(surface_recon_wf, morph_grayords_wf, [
(sphere_reg_wf, anat_fsLR_resampling_wf, [
('outputnode.sphere_reg', 'inputnode.sphere_reg'),
('outputnode.sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR')]),
(surface_recon_wf, anat_fsLR_resampling_wf, [
('outputnode.subject_id', 'inputnode.subject_id'),
('outputnode.subjects_dir', 'inputnode.subjects_dir'),
]),
(morph_grayords_wf, anat_derivatives_wf, [
('outputnode.surfaces', 'inputnode.surfaces'),
('outputnode.morphometrics', 'inputnode.morphometrics')]),
(anat_fsLR_resampling_wf, anat_derivatives_wf, [
("outputnode.cifti_morph", "inputnode.cifti_morph"),
("outputnode.cifti_metadata", "inputnode.cifti_metadata"),
]),
("outputnode.cifti_metadata", "inputnode.cifti_metadata")]),
(anat_fsLR_resampling_wf, outputnode, [
("outputnode.midthickness_fsLR", "midthickness_fsLR")])
])
# fmt:on

Expand Down
4 changes: 2 additions & 2 deletions nibabies/workflows/anatomical/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,8 @@ def init_anat_derivatives_wf(
(inputnode, ds_regs, [('sphere_reg', 'in_file'),
('source_files', 'source_file')]),
(name_regs, ds_regs, [('hemi', 'hemi')]),
(inputnode, name_reg_fsLR, [('sphere_reg', 'in_file')]),
(inputnode, ds_reg_fsLR, [('sphere_reg', 'in_file'),
(inputnode, name_reg_fsLR, [('sphere_reg_fsLR', 'in_file')]),
(inputnode, ds_reg_fsLR, [('sphere_reg_fsLR', 'in_file'),
('source_files', 'source_file')]),
(name_reg_fsLR, ds_reg_fsLR, [('hemi', 'hemi')]),
(inputnode, name_morphs, [('morphometrics', 'in_file')]),
Expand Down
Loading