Skip to content

Commit ba18576

Browse files
committed
fix: Retrieve pe-dir from moving image, use fixed resolution/orientation
1 parent 7464e21 commit ba18576

File tree

2 files changed

+65
-22
lines changed

2 files changed

+65
-22
lines changed

sdcflows/workflows/fit/syn.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ def init_syn_sdc_wf(
301301
(anat_dilmsk, amask2epi, [("out_file", "input_image")]),
302302
(amask2epi, epi_umask, [("output_image", "in2")]),
303303
(readout_time, warp_dir, [("pe_direction", "pe_dir")]),
304+
(inputnode, vox_params, [("anat_ref", "fixed_image")]),
304305
(clip_epi, vox_params, [("out_file", "moving_image")]),
305306
(readout_time, vox_params, [("pe_direction", "pe_dir")]),
306307
(warp_dir, syn, [("out", "restrict_deformation")]),
@@ -613,14 +614,21 @@ def _warp_dir(fixed_image, pe_dir, nlevels=3):
613614
return nlevels * [retval.tolist()]
614615

615616

616-
def _mm2vox(moving_image, pe_dir, registration_config):
617+
def _mm2vox(moving_image, fixed_image, pe_dir, registration_config):
617618
import nibabel as nb
618619

619620
params = registration_config['transform_parameters']
620621

621-
img = nb.load(moving_image)
622-
zooms = nb.affines.voxel_sizes(img.affine)
623-
pe_res = zooms["ijk".index(pe_dir[0])]
622+
moving = nb.load(moving_image)
623+
# Use duplicate axcodes to ignore sign
624+
moving_axcodes = nb.aff2axcodes(moving.affine, ["RR", "AA", "SS"])
625+
moving_pe_axis = moving_axcodes["ijk".index(pe_dir[0])]
626+
627+
fixed = nb.load(fixed_image)
628+
fixed_axcodes = nb.aff2axcodes(fixed.affine, ["RR", "AA", "SS"])
629+
630+
zooms = nb.affines.voxel_sizes(fixed.affine)
631+
pe_res = zooms[fixed_axcodes.index(moving_pe_axis)]
624632

625633
return [
626634
[*level_params[:2], level_params[2] / pe_res]

sdcflows/workflows/fit/tests/test_syn.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@
3030
from nipype.pipeline import engine as pe
3131

3232
from .... import data
33-
from ..syn import init_syn_sdc_wf, init_syn_preprocessing_wf, _adjust_zooms, _set_dtype, _mm2vox
33+
from ..syn import (
34+
init_syn_sdc_wf,
35+
init_syn_preprocessing_wf,
36+
_adjust_zooms,
37+
_set_dtype,
38+
_mm2vox,
39+
)
3440

3541

3642
@pytest.mark.veryslow
@@ -260,25 +266,54 @@ def test_ensure_dtype(in_dtype, out_dtype, tmpdir):
260266
assert out_file == f"{in_dtype}_{out_dtype}.nii.gz"
261267

262268

263-
def test_mm2vox(tmp_path):
264-
img = nb.Nifti1Image(np.zeros((10, 10, 10)), np.diag((2, 3, 4, 1)))
265-
img_file = tmp_path / "test.nii.gz"
266-
img.to_filename(img_file)
269+
def axcodes2aff(axcodes):
270+
"""Return an affine matrix from axis codes."""
271+
return nb.orientations.inv_ornt_aff(
272+
nb.orientations.ornt_transform(
273+
nb.orientations.axcodes2ornt("RAS"),
274+
nb.orientations.axcodes2ornt(axcodes),
275+
),
276+
(10, 10, 10),
277+
)
267278

268-
config = json.loads(data.load.readable("sd_syn.json").read_text())
269279

270-
params = config['transform_parameters']
271-
mm_values = np.array([level[2] for level in params])
280+
@pytest.mark.parametrize(
281+
("fixed_ornt", "moving_ornt", "ijk", "index"),
282+
[
283+
("RAS", "RAS", "i", 0),
284+
("RAS", "RAS", "j", 1),
285+
("RAS", "RAS", "k", 2),
286+
("RAS", "PSL", "i", 1),
287+
("RAS", "PSL", "j", 2),
288+
("RAS", "PSL", "k", 0),
289+
("PSL", "RAS", "i", 2),
290+
("PSL", "RAS", "j", 0),
291+
("PSL", "RAS", "k", 1),
292+
],
293+
)
294+
def test_mm2vox(tmp_path, fixed_ornt, moving_ornt, ijk, index):
295+
fixed_path = tmp_path / "fixed.nii.gz"
296+
moving_path = tmp_path / "moving.nii.gz"
297+
298+
# Use separate zooms to make identifying the conversion easier
299+
fixed_aff = np.diag((2, 3, 4, 1))
300+
nb.save(
301+
nb.Nifti1Image(np.zeros((10, 10, 10)), axcodes2aff(fixed_ornt) @ fixed_aff),
302+
fixed_path,
303+
)
304+
nb.save(
305+
nb.Nifti1Image(np.zeros((10, 10, 10)), axcodes2aff(moving_ornt)),
306+
moving_path,
307+
)
272308

273-
vox_params_i = _mm2vox(str(img_file), 'i', config)
274-
vox_values_i = [level[2] for level in vox_params_i]
275-
assert [mm_level[:2] == vox_level[:2] for mm_level, vox_level in zip(params, vox_params_i)]
276-
assert np.array_equal(vox_values_i, mm_values / 2)
309+
config = json.loads(data.load.readable("sd_syn.json").read_text())
277310

278-
vox_params_j = _mm2vox(str(img_file), 'j', config)
279-
vox_values_j = [level[2] for level in vox_params_j]
280-
assert np.array_equal(vox_values_j, mm_values / 3)
311+
params = config["transform_parameters"]
312+
mm_values = np.array([level[2] for level in params])
281313

282-
vox_params_k = _mm2vox(str(img_file), 'k', config)
283-
vox_values_k = [level[2] for level in vox_params_k]
284-
assert np.array_equal(vox_values_k, mm_values / 4)
314+
vox_params = _mm2vox(str(moving_path), str(fixed_path), ijk, config)
315+
vox_values = [level[2] for level in vox_params]
316+
assert [
317+
mm_level[:2] == vox_level[:2] for mm_level, vox_level in zip(params, vox_params)
318+
]
319+
assert np.array_equal(vox_values, mm_values / [2, 3, 4][index])

0 commit comments

Comments
 (0)