Skip to content

Commit e42a7b4

Browse files
committed
ENH: Add interface for reorienting images
1 parent cd62de6 commit e42a7b4

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

niworkflows/interfaces/nibabel.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
# https://www.nipreps.org/community/licensing/
2222
#
2323
"""Nibabel-based interfaces."""
24+
from pathlib import Path
25+
2426
import numpy as np
2527
import nibabel as nb
2628
from nipype import logging
@@ -494,6 +496,69 @@ def _run_interface(self, runtime):
494496
return runtime
495497

496498

499+
class ReorientImageInputSpec(BaseInterfaceInputSpec):
500+
in_file = File(exists=True, mandatory=True, desc="Moving file")
501+
target_file = File(
502+
exists=True, xor=["target_orientation"], desc="Reference file to reorient to"
503+
)
504+
target_orientation = traits.Str(
505+
xor=["target_file"], desc="Axis codes of coordinate system to reorient to"
506+
)
507+
508+
509+
class ReorientImageOutputSpec(TraitedSpec):
510+
out_file = File(desc="Reoriented file")
511+
512+
513+
class ReorientImage(SimpleInterface):
514+
input_spec = ReorientImageInputSpec
515+
output_spec = ReorientImageOutputSpec
516+
517+
def _run_interface(self, runtime):
518+
self._results["out_file"] = reorient_image(
519+
self.inputs.in_file,
520+
target_file=self.inputs.target_file,
521+
target_ornt=self.inputs.target_orientation,
522+
)
523+
return runtime
524+
525+
526+
def reorient_image(
527+
in_file: str, *, target_file: str = None, target_ornt: str = None, newpath: str = None
528+
) -> str:
529+
"""
530+
Reorient an image.
531+
532+
New orientation targets can be either another image, or a string representation of the
533+
orientation axis.
534+
535+
Parameters
536+
----------
537+
in_file : Image to be reoriented
538+
target_file : Reference image of desired orientation
539+
target_ornt : Orientation denoted by the first letter of each axis (i.e., "RAS", "LPI")
540+
"""
541+
import nibabel as nb
542+
543+
img = nb.load(in_file)
544+
img_axcodes = nb.aff2axcodes(img.affine)
545+
in_ornt = nb.orientations.axcodes2ornt(img_axcodes)
546+
547+
if target_file:
548+
target_img = nb.load(target_file)
549+
target_ornt = nb.aff2axcodes(target_img.affine)
550+
551+
out_ornt = nb.orientations.axcodes2ornt(target_ornt)
552+
ornt_xfm = nb.orientations.ornt_transform(in_ornt, out_ornt)
553+
reoriented = img.as_reoriented(ornt_xfm)
554+
555+
if newpath is None:
556+
newpath = Path()
557+
out_file = str((Path(newpath) / "reoriented.nii.gz").absolute())
558+
reoriented.to_filename(out_file)
559+
return out_file
560+
561+
497562
def _gen_reference(
498563
fixed_image,
499564
moving_image,

niworkflows/interfaces/tests/test_nibabel.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import nibabel as nb
3030
import pytest
3131

32-
from ..nibabel import Binarize, ApplyMask, SplitSeries, MergeSeries, MergeROIs, MapLabels
32+
from ..nibabel import (
33+
Binarize, ApplyMask, SplitSeries, MergeSeries, MergeROIs, MapLabels, ReorientImage
34+
)
3335

3436

3537
@pytest.fixture
@@ -280,3 +282,55 @@ def test_map_labels(tmpdir, data, mapping, tojson, expected):
280282
Path(in_file).unlink()
281283
if tojson:
282284
Path(map_file).unlink()
285+
286+
287+
def create_save_img(ornt: str):
288+
data = np.random.rand(2, 2, 2)
289+
img = nb.Nifti1Image(data, affine=np.eye(4))
290+
# img will alway be in RAS at the start
291+
ras = nb.orientations.axcodes2ornt("RAS")
292+
if ornt != 'RAS':
293+
new = nb.orientations.axcodes2ornt(ornt)
294+
xfm = nb.orientations.ornt_transform(ras, new)
295+
img = img.as_reoriented(xfm)
296+
out_file = f'{uuid.uuid4()}.nii.gz'
297+
img.to_filename(out_file)
298+
return out_file
299+
300+
301+
@pytest.mark.parametrize(
302+
"in_ornt,out_ornt",
303+
[
304+
("RAS", "RAS"),
305+
("RAS", "LAS"),
306+
("LAS", "RAS"),
307+
("RAS", "RPI"),
308+
("LPI", "RAS"),
309+
],
310+
)
311+
def test_reorient_image(tmpdir, in_ornt, out_ornt):
312+
tmpdir.chdir()
313+
314+
in_file = create_save_img(ornt=in_ornt)
315+
in_img = nb.load(in_file)
316+
assert ''.join(nb.aff2axcodes(in_img.affine)) == in_ornt
317+
318+
# test string representation
319+
res = ReorientImage(in_file=in_file, target_orientation=out_ornt).run()
320+
out_file = res.outputs.out_file
321+
out_img = nb.load(out_file)
322+
assert ''.join(nb.aff2axcodes(out_img.affine)) == out_ornt
323+
Path(out_file).unlink()
324+
325+
# test with target file
326+
target_file = create_save_img(ornt=out_ornt)
327+
target_img = nb.load(target_file)
328+
assert ''.join(nb.aff2axcodes(target_img.affine)) == out_ornt
329+
res = ReorientImage(in_file=in_file, target_file=target_file).run()
330+
out_file = res.outputs.out_file
331+
out_img = nb.load(out_file)
332+
assert ''.join(nb.aff2axcodes(out_img.affine)) == out_ornt
333+
334+
# cleanup
335+
for f in (in_file, target_file, out_file):
336+
Path(f).unlink()

0 commit comments

Comments
 (0)