Skip to content

Commit 9a135f4

Browse files
authored
Merge pull request #363 from effigies/enh/simplify-ribbon
RF: Replace most of anat_ribbon_wf with a Python function
2 parents 244f1b2 + c3f51ad commit 9a135f4

13 files changed

+183
-105
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ jobs:
282282

283283
test:
284284
<<: *machine_defaults
285+
environment:
286+
- FS_LICENSE: /tmp/fslicense/license.txt
285287
steps:
286288
- attach_workspace:
287289
at: /tmp
@@ -811,6 +813,7 @@ workflows:
811813
context:
812814
- nipreps-common
813815
requires:
816+
- get_data
814817
- build
815818
filters:
816819
branches:

smriprep/interfaces/surf.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,32 @@ def _run_interface(self, runtime):
157157
return runtime
158158

159159

160+
class MakeRibbonInputSpec(TraitedSpec):
161+
white_distvols = traits.List(
162+
File(exists=True), minlen=2, maxlen=2, desc="White matter distance volumes"
163+
)
164+
pial_distvols = traits.List(
165+
File(exists=True), minlen=2, maxlen=2, desc="Pial matter distance volumes"
166+
)
167+
168+
169+
class MakeRibbonOutputSpec(TraitedSpec):
170+
ribbon = File(desc="Binary ribbon mask")
171+
172+
173+
class MakeRibbon(SimpleInterface):
174+
"""Create a binary ribbon mask from white and pial distance volumes."""
175+
176+
input_spec = MakeRibbonInputSpec
177+
output_spec = MakeRibbonOutputSpec
178+
179+
def _run_interface(self, runtime):
180+
self._results["ribbon"] = make_ribbon(
181+
self.inputs.white_distvols, self.inputs.pial_distvols, newpath=runtime.cwd
182+
)
183+
return runtime
184+
185+
160186
def normalize_surfs(
161187
in_file: str, transform_file: str | None, newpath: Optional[str] = None
162188
) -> str:
@@ -209,7 +235,7 @@ def normalize_surfs(
209235
for RAS in "RAS":
210236
pointset.meta.pop(f"VolGeom{XYZC}_{RAS}", None)
211237

212-
if newpath is not None:
238+
if newpath is None:
213239
newpath = os.getcwd()
214240
out_file = os.path.join(newpath, fname)
215241
img.to_filename(out_file)
@@ -233,8 +259,32 @@ def fix_gifti_metadata(in_file: str, newpath: Optional[str] = None) -> str:
233259
if pointset.meta.get("GeometricType") == "Sphere":
234260
pointset.meta["GeometricType"] = "Spherical"
235261

236-
if newpath is not None:
262+
if newpath is None:
237263
newpath = os.getcwd()
238264
out_file = os.path.join(newpath, os.path.basename(in_file))
239265
img.to_filename(out_file)
240266
return out_file
267+
268+
269+
def make_ribbon(
270+
white_distvols: list[str],
271+
pial_distvols: list[str],
272+
newpath: Optional[str] = None,
273+
) -> str:
274+
base_img = nb.load(white_distvols[0])
275+
header = base_img.header
276+
header.set_data_dtype("uint8")
277+
278+
ribbons = [
279+
(np.array(nb.load(white).dataobj) > 0) & (np.array(nb.load(pial).dataobj) < 0)
280+
for white, pial in zip(white_distvols, pial_distvols)
281+
]
282+
283+
if newpath is None:
284+
newpath = os.getcwd()
285+
out_file = os.path.join(newpath, "ribbon.nii.gz")
286+
287+
ribbon_data = ribbons[0] | ribbons[1]
288+
ribbon = base_img.__class__(ribbon_data, base_img.affine, header)
289+
ribbon.to_filename(out_file)
290+
return out_file

smriprep/interfaces/tests/__init__.py

Whitespace-only changes.
Binary file not shown.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import nibabel as nb
2+
from nipype.pipeline import engine as pe
3+
import numpy as np
4+
5+
from ...data import load_resource
6+
from ..surf import MakeRibbon
7+
8+
9+
def test_MakeRibbon(tmp_path):
10+
res_template = "{path}/sub-fsaverage_res-4_hemi-{hemi}_desc-cropped_{surf}dist.nii.gz"
11+
white, pial = [
12+
[
13+
load_resource(
14+
res_template.format(path="../interfaces/tests/data", hemi=hemi, surf=surf)
15+
)
16+
for hemi in "LR"
17+
]
18+
for surf in ("wm", "pial")
19+
]
20+
21+
make_ribbon = pe.Node(
22+
MakeRibbon(white_distvols=white, pial_distvols=pial),
23+
name="make_ribbon",
24+
base_dir=tmp_path,
25+
)
26+
27+
result = make_ribbon.run()
28+
29+
ribbon = nb.load(result.outputs.ribbon)
30+
expected = nb.load(
31+
load_resource("../interfaces/tests/data/sub-fsaverage_res-4_desc-cropped_ribbon.nii.gz")
32+
)
33+
34+
assert ribbon.shape == expected.shape
35+
assert np.allclose(ribbon.affine, expected.affine)
36+
assert np.array_equal(ribbon.dataobj, expected.dataobj)

smriprep/workflows/anatomical.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def init_anat_preproc_wf(
310310
('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'),
311311
]),
312312
(anat_fit_wf, anat_ribbon_wf, [
313-
('outputnode.t1w_mask', 'inputnode.t1w_mask'),
313+
('outputnode.t1w_mask', 'inputnode.ref_file'),
314314
]),
315315
(surface_derivatives_wf, anat_ribbon_wf, [
316316
('outputnode.white', 'inputnode.white'),

0 commit comments

Comments
 (0)