Skip to content

Commit a4c7720

Browse files
committed
RF: Reorient PE dir along with image pre-TOPUP
1 parent 0a44004 commit a4c7720

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

sdcflows/interfaces/utils.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# https://www.nipreps.org/community/licensing/
2222
#
2323
"""Utilities."""
24+
from itertools import product
2425
from nipype.interfaces.base import (
2526
BaseInterfaceInputSpec,
2627
TraitedSpec,
@@ -147,6 +148,84 @@ def _run_interface(self, runtime):
147148
return runtime
148149

149150

151+
class _ReorientImageAndMetadataInputSpec(TraitedSpec):
152+
in_file = File(exists=True, mandatory=True, desc="Input 3- or 4D image")
153+
target_orientation = traits.Str(
154+
desc="Axis codes of coordinate system to reorient to"
155+
)
156+
pe_dir = InputMultiObject(
157+
traits.Enum(
158+
*["".join(p) for p in product("ijkxyz", ("", "-"))],
159+
mandatory=True,
160+
desc="Phase encoding direction",
161+
)
162+
)
163+
164+
165+
class _ReorientImageAndMetadataOutputSpec(TraitedSpec):
166+
out_file = File(desc="Reoriented image")
167+
pe_dir = OutputMultiObject(
168+
traits.Enum(
169+
*["".join(p) for p in product("ijkxyz", ("", "-"))],
170+
desc="Phase encoding direction in reoriented image",
171+
)
172+
)
173+
174+
175+
class ReorientImageAndMetadata(SimpleInterface):
176+
input_spec = _ReorientImageAndMetadataInputSpec
177+
output_spec = _ReorientImageAndMetadataOutputSpec
178+
179+
def _run_interface(self, runtime):
180+
import numpy as np
181+
import nibabel as nb
182+
from nipype.utils.filemanip import fname_presuffix
183+
184+
target = self.inputs.target_orientation.upper()
185+
if not all(code in "RASLPI" for code in target):
186+
raise ValueError(
187+
"Invalid orientation code {self.inputs.target_orientation}"
188+
)
189+
190+
img = nb.load(self.inputs.in_file)
191+
img2target = nb.orientations.ornt_transform(
192+
nb.io_orientation(img.affine),
193+
nb.orientations.axcodes2ornt(self.inputs.target_orientation),
194+
).astype(int)
195+
196+
# Identity transform
197+
if np.array_equal(img2target, [[0, 1], [1, 1], [2, 1]]):
198+
self._results = dict(
199+
out_file=self.inputs.in_file,
200+
pe_dir=self.inputs.pe_dir,
201+
)
202+
203+
reoriented = img.as_reoriented(img2target)
204+
205+
pe_dirs = []
206+
for pe_dir in self.inputs.pe_dir:
207+
directions = "ijk" if pe_dir[0] in "ijk" else "xyz"
208+
209+
orig_axis = directions.index(pe_dir[0])
210+
orig_flip = pe_dir[1:] == "-"
211+
212+
reoriented_axis = directions[img2target[orig_axis, 0]]
213+
reoriented_flip = orig_flip ^ (img2target[orig_axis, 1] == -1)
214+
215+
pe_dirs.append(reoriented_axis + ("-" if reoriented_flip else ""))
216+
217+
self._results = dict(
218+
out_file=fname_presuffix(
219+
self.inputs.in_file, suffix=target, newpath=runtime.cwd
220+
),
221+
pe_dir=pe_dirs,
222+
)
223+
224+
reoriented.to_filename(self._results["out_file"])
225+
226+
return runtime
227+
228+
150229
class _ConvertWarpInputSpec(BaseInterfaceInputSpec):
151230
in_file = File(exists=True, mandatory=True, desc="output of 3dQwarp")
152231

sdcflows/workflows/fit/pepolar.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def init_topup_wf(
9292

9393
from ...utils.misc import front as _front
9494
from ...interfaces.epi import GetReadoutTime, SortPEBlips
95-
from ...interfaces.utils import UniformGrid, PadSlices
95+
from ...interfaces.utils import UniformGrid, PadSlices, ReorientImageAndMetadata
9696
from ...interfaces.bspline import TOPUPCoeffReorient
9797
from ..ancillary import init_brainextraction_wf
9898

@@ -145,7 +145,7 @@ def init_topup_wf(
145145
setwise_avg = pe.Node(RobustAverage(num_threads=omp_nthreads), name="setwise_avg")
146146
# The core of the implementation
147147
# Feed the input images in LAS orientation, so FSL does not run funky reorientations
148-
to_las = pe.Node(ReorientImage(target_orientation="LAS"), name="to_las")
148+
to_las = pe.Node(ReorientImageAndMetadata(target_orientation="LAS"), name="to_las")
149149
topup = pe.Node(
150150
TOPUP(
151151
config=_pkg_fname("sdcflows", f"data/flirtsch/b02b0{'_quick' * sloppy}.cnf")
@@ -172,16 +172,19 @@ def init_topup_wf(
172172
(regrid, sort_pe_blips, [("out_data", "in_data")]),
173173
(readout_time, sort_pe_blips, [("readout_time", "readout_times"),
174174
("pe_dir_fsl", "pe_dirs_fsl")]),
175-
(sort_pe_blips, topup, [("readout_times", "readout_times"),
176-
("pe_dirs_fsl", "encoding_direction")]),
177-
(sort_pe_blips, fix_coeff, [(("pe_dirs", _front), "pe_dir")]),
175+
(sort_pe_blips, topup, [("readout_times", "readout_times")]),
178176
(setwise_avg, fix_coeff, [("out_file", "fmap_ref")]),
179177
(sort_pe_blips, concat_blips, [("out_data", "in_files")]),
180178
(concat_blips, pad_blip_slices, [("out_file", "in_file")]),
181179
(pad_blip_slices, setwise_avg, [("out_file", "in_file")]),
182180
(setwise_avg, to_las, [("out_hmc_volumes", "in_file")]),
183-
(to_las, topup, [("out_file", "in_file")]),
181+
(sort_pe_blips, to_las, [("pe_dirs_fsl", "pe_dir")]),
182+
(to_las, topup, [
183+
("out_file", "in_file"),
184+
("pe_dir", "encoding_direction"),
185+
]),
184186
(topup, fix_coeff, [("out_fieldcoef", "in_coeff")]),
187+
(to_las, fix_coeff, [(("pe_dir", _front), "pe_dir")]),
185188
(topup, outputnode, [("out_jacs", "jacobians"),
186189
("out_mats", "xfms")]),
187190
(ref_average, brainextraction_wf, [("out_file", "inputnode.in_file")]),

0 commit comments

Comments
 (0)