Skip to content

Commit 4de3897

Browse files
authored
Merge pull request #278 from madisoth/enh/project-goodvoxels-new
ENH: Add option to exclude projecting high variance voxels to surface (update of #235)
2 parents a89a099 + 6d80e92 commit 4de3897

File tree

17 files changed

+1101
-59
lines changed

17 files changed

+1101
-59
lines changed

.circleci/bcp_anat_outputs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-brain_mask.json
1717
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-brain_mask.nii.gz
1818
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T1w.json
1919
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T1w.nii.gz
20+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-ribbon_mask.nii.gz
2021
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_dseg.nii.gz
2122
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-fsnative_to-T1w_mode-image_xfm.txt
2223
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T1w_mode-image_xfm.h5

.circleci/bcp_full_outputs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-brain_mask.json
1717
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-brain_mask.nii.gz
1818
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T1w.json
1919
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T1w.nii.gz
20+
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-ribbon_mask.nii.gz
2021
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_dseg.nii.gz
2122
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-fsnative_to-T1w_mode-image_xfm.txt
2223
sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T1w_mode-image_xfm.h5

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ ENV PATH="/opt/MCRIBS/bin:/opt/MCRIBS/MIRTK/MIRTK-install/bin:/opt/MCRIBS/MIRTK/
288288

289289
# Precaching atlases
290290
COPY scripts/fetch_templates.py fetch_templates.py
291-
RUN ${CONDA_PYTHON} fetch_templates.py && \
291+
RUN ${CONDA_PYTHON} -m pip install --no-cache-dir --upgrade templateflow && \
292+
${CONDA_PYTHON} fetch_templates.py && \
292293
rm fetch_templates.py && \
293294
find $HOME/.cache/templateflow -type d -exec chmod go=u {} + && \
294295
find $HOME/.cache/templateflow -type f -exec chmod go=u {} +

nibabies/cli/parser.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,15 @@ def _slice_time_ref(value, parser):
333333
help="Replace medial wall values with NaNs on functional GIFTI files. Only "
334334
"performed for GIFTI files mapped to a freesurfer subject (fsaverage or fsnative).",
335335
)
336+
g_conf.add_argument(
337+
"--project-goodvoxels",
338+
required=False,
339+
action="store_true",
340+
default=False,
341+
help="Exclude voxels whose timeseries have locally high coefficient of variation "
342+
"from surface resampling. Only performed for GIFTI files mapped to a freesurfer subject "
343+
"(fsaverage or fsnative).",
344+
)
336345
g_conf.add_argument(
337346
"--slice-time-ref",
338347
required=False,

nibabies/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,8 @@ class workflow(_Config):
553553
"""Run FreeSurfer ``recon-all`` with the ``-logitudinal`` flag."""
554554
medial_surface_nan = None
555555
"""Fill medial surface with :abbr:`NaNs (not-a-number)` when sampling."""
556+
project_goodvoxels = False
557+
"""Exclude voxels with locally high coefficient of variation from sampling."""
556558
regressors_all_comps = None
557559
"""Return all CompCor components."""
558560
regressors_dvars_th = None

nibabies/data/tests/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ hires = true
5252
ignore = [ "slicetiming",]
5353
longitudinal = false
5454
medial_surface_nan = false
55+
project_goodvoxels = false
5556
regressors_all_comps = false
5657
regressors_dvars_th = 1.5
5758
regressors_fd_th = 0.5

nibabies/interfaces/metric.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
# -*- coding: utf-8 -*-
2+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3+
# vi: set ft=python sts=4 ts=4 sw=4 et:
4+
"""This module provides interfaces for workbench surface commands"""
5+
import os
6+
7+
from nipype import logging
8+
from nipype.interfaces.base import CommandLineInputSpec, File, TraitedSpec, traits
9+
from nipype.interfaces.workbench.base import WBCommand
10+
11+
iflogger = logging.getLogger("nipype.interface")
12+
13+
14+
class MetricDilateInputSpec(CommandLineInputSpec):
15+
in_file = File(
16+
exists=True,
17+
mandatory=True,
18+
argstr="%s ",
19+
position=0,
20+
desc="the metric to dilate",
21+
)
22+
23+
surf_file = File(
24+
exists=True,
25+
mandatory=True,
26+
argstr="%s ",
27+
position=1,
28+
desc="the surface to compute on",
29+
)
30+
31+
distance = traits.Float(
32+
mandatory=True,
33+
argstr="%f ",
34+
position=2,
35+
desc="distance in mm to dilate",
36+
)
37+
38+
out_file = File(
39+
name_source=["in_file"],
40+
name_template="%s.func.gii",
41+
keep_extension=False,
42+
argstr="%s ",
43+
position=3,
44+
desc="output - the output metric",
45+
)
46+
47+
bad_vertex_roi_file = File(
48+
argstr="-bad-vertex-roi %s ",
49+
position=4,
50+
desc="metric file, positive values denote vertices to have their values replaced",
51+
)
52+
53+
data_roi_file = File(
54+
argstr="-data-roi %s ",
55+
position=5,
56+
desc="metric file, positive values denote vertices that have data",
57+
)
58+
59+
column = traits.Int(
60+
position=6,
61+
argstr="-column %d ",
62+
desc="the column number",
63+
)
64+
65+
nearest = traits.Bool(
66+
position=7,
67+
argstr="-nearest ",
68+
desc="use the nearest good value instead of a weighted average",
69+
)
70+
71+
linear = traits.Bool(
72+
position=8,
73+
argstr="-linear ",
74+
desc="fill in values with linear interpolation along strongest gradient",
75+
)
76+
77+
exponent = traits.Float(
78+
argstr="-exponent %f ",
79+
position=9,
80+
default=6.0,
81+
desc="exponent n to use in (area / (distance ^ n)) as the "
82+
"weighting function (default 6)",
83+
)
84+
85+
corrected_areas = File(
86+
argstr="-corrected-areas %s ",
87+
position=10,
88+
desc="vertex areas to use instead of computing them from the surface",
89+
)
90+
91+
legacy_cutoff = traits.Bool(
92+
position=11,
93+
argstr="-legacy-cutoff ",
94+
desc="use the v1.3.2 method of choosing how many vertices to "
95+
"use when calulating the dilated value with weighted method",
96+
)
97+
98+
99+
class MetricDilateOutputSpec(TraitedSpec):
100+
out_file = File(exists=True, desc="output file")
101+
102+
103+
class MetricDilate(WBCommand):
104+
"""
105+
For all data values designated as bad, if they neighbor a good value or
106+
are within the specified distance of a good value in the same kind of
107+
model, replace the value with a distance weighted average of nearby good
108+
values, otherwise set the value to zero. If -nearest is specified, it
109+
will use the value from the closest good value within range instead of a
110+
weighted average. When the input file contains label data, nearest
111+
dilation is used on the surface, and weighted popularity is used in the
112+
volume.
113+
114+
The -corrected-areas options are intended for dilating on group average
115+
surfaces, but it is only an approximate correction for the reduction of
116+
structure in a group average surface.
117+
118+
If -bad-vertex-roi is specified, all values, including those with
119+
value zero, are good, except for locations with a positive value in the
120+
ROI. If it is not specified, only values equal to zero are bad.
121+
"""
122+
123+
input_spec = MetricDilateInputSpec
124+
output_spec = MetricDilateOutputSpec
125+
_cmd = "wb_command -metric-dilate "
126+
127+
128+
class MetricResampleInputSpec(CommandLineInputSpec):
129+
in_file = File(
130+
exists=True,
131+
mandatory=True,
132+
argstr="%s",
133+
position=0,
134+
desc="The metric file to resample",
135+
)
136+
current_sphere = File(
137+
exists=True,
138+
mandatory=True,
139+
argstr="%s",
140+
position=1,
141+
desc="A sphere surface with the mesh that the metric is currently on",
142+
)
143+
new_sphere = File(
144+
exists=True,
145+
mandatory=True,
146+
argstr="%s",
147+
position=2,
148+
desc="A sphere surface that is in register with <current-sphere> and"
149+
" has the desired output mesh",
150+
)
151+
method = traits.Enum(
152+
"ADAP_BARY_AREA",
153+
"BARYCENTRIC",
154+
argstr="%s",
155+
mandatory=True,
156+
position=3,
157+
desc="The method name - ADAP_BARY_AREA method is recommended for"
158+
" ordinary metric data, because it should use all data while"
159+
" downsampling, unlike BARYCENTRIC. If ADAP_BARY_AREA is used,"
160+
" exactly one of area_surfs or area_metrics must be specified",
161+
)
162+
out_file = File(
163+
name_source=["new_sphere"],
164+
name_template="%s.out",
165+
keep_extension=True,
166+
argstr="%s",
167+
position=4,
168+
desc="The output metric",
169+
)
170+
area_surfs = traits.Bool(
171+
position=5,
172+
argstr="-area-surfs",
173+
xor=["area_metrics"],
174+
desc="Specify surfaces to do vertex area correction based on",
175+
)
176+
area_metrics = traits.Bool(
177+
position=5,
178+
argstr="-area-metrics",
179+
xor=["area_surfs"],
180+
desc="Specify vertex area metrics to do area correction based on",
181+
)
182+
current_area = File(
183+
exists=True,
184+
position=6,
185+
argstr="%s",
186+
desc="A relevant anatomical surface with <current-sphere> mesh OR"
187+
" a metric file with vertex areas for <current-sphere> mesh",
188+
)
189+
new_area = File(
190+
exists=True,
191+
position=7,
192+
argstr="%s",
193+
desc="A relevant anatomical surface with <current-sphere> mesh OR"
194+
" a metric file with vertex areas for <current-sphere> mesh",
195+
)
196+
roi_metric = File(
197+
exists=True,
198+
position=8,
199+
argstr="-current-roi %s",
200+
desc="Input roi on the current mesh used to exclude non-data vertices",
201+
)
202+
valid_roi_out = traits.Bool(
203+
position=9,
204+
argstr="-valid-roi-out",
205+
desc="Output the ROI of vertices that got data from valid source vertices",
206+
)
207+
largest = traits.Bool(
208+
position=10,
209+
argstr="-largest",
210+
desc="Use only the value of the vertex with the largest weight",
211+
)
212+
213+
214+
class MetricResampleOutputSpec(TraitedSpec):
215+
out_file = File(exists=True, desc="the output metric")
216+
roi_file = File(desc="ROI of vertices that got data from valid source vertices")
217+
218+
219+
class MetricResample(WBCommand):
220+
"""
221+
Resample a metric file to a different mesh
222+
223+
Resamples a metric file, given two spherical surfaces that are in
224+
register. If ``ADAP_BARY_AREA`` is used, exactly one of -area-surfs or
225+
``-area-metrics`` must be specified.
226+
227+
The ``ADAP_BARY_AREA`` method is recommended for ordinary metric data,
228+
because it should use all data while downsampling, unlike ``BARYCENTRIC``.
229+
The recommended areas option for most data is individual midthicknesses
230+
for individual data, and averaged vertex area metrics from individual
231+
midthicknesses for group average data.
232+
233+
The ``-current-roi`` option only masks the input, the output may be slightly
234+
dilated in comparison, consider using ``-metric-mask`` on the output when
235+
using ``-current-roi``.
236+
237+
The ``-largest option`` results in nearest vertex behavior when used with
238+
``BARYCENTRIC``. When resampling a binary metric, consider thresholding at
239+
0.5 after resampling rather than using ``-largest``.
240+
"""
241+
242+
input_spec = MetricResampleInputSpec
243+
output_spec = MetricResampleOutputSpec
244+
_cmd = "wb_command -metric-resample"
245+
246+
def _format_arg(self, opt, spec, val):
247+
if opt in ["current_area", "new_area"]:
248+
if not self.inputs.area_surfs and not self.inputs.area_metrics:
249+
raise ValueError(
250+
"{} was set but neither area_surfs or" " area_metrics were set".format(opt)
251+
)
252+
if opt == "method":
253+
if (
254+
val == "ADAP_BARY_AREA"
255+
and not self.inputs.area_surfs
256+
and not self.inputs.area_metrics
257+
):
258+
raise ValueError("Exactly one of area_surfs or area_metrics" " must be specified")
259+
if opt == "valid_roi_out" and val:
260+
# generate a filename and add it to argstr
261+
roi_out = self._gen_filename(self.inputs.in_file, suffix="_roi")
262+
iflogger.info("Setting roi output file as", roi_out)
263+
spec.argstr += " " + roi_out
264+
return super(MetricResample, self)._format_arg(opt, spec, val)
265+
266+
def _list_outputs(self):
267+
outputs = super(MetricResample, self)._list_outputs()
268+
if self.inputs.valid_roi_out:
269+
roi_file = self._gen_filename(self.inputs.in_file, suffix="_roi")
270+
outputs["roi_file"] = os.path.abspath(roi_file)
271+
return outputs

0 commit comments

Comments
 (0)