Skip to content

Commit bc0b4d7

Browse files
authored
Merge pull request #534 from fliem/conform-fix-zooms
FIX: Error conforming T1w images with differing zooms before recon-all Resolves: nipreps/smriprep#70
2 parents b8ef4b6 + 5dd70f1 commit bc0b4d7

File tree

2 files changed

+53
-15
lines changed

2 files changed

+53
-15
lines changed

niworkflows/interfaces/images.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -305,31 +305,37 @@ def _run_interface(self, runtime):
305305
xyz_unit = "mm"
306306

307307
# Set a 0.05mm threshold to performing rescaling
308-
atol = {"meter": 1e-5, "mm": 0.01, "micron": 10}[xyz_unit]
309-
310-
# Rescale => change zooms
311-
# Resize => update image dimensions
312-
rescale = not np.allclose(zooms, target_zooms, atol=atol)
308+
atol_gross = {"meter": 5e-5, "mm": 0.05, "micron": 50}[xyz_unit]
309+
# if 0.01 > difference > 0.001mm, freesurfer won't be able to merge the images
310+
atol_fine = {"meter": 1e-6, "mm": 0.001, "micron": 1}[xyz_unit]
311+
312+
# Update zooms => Modify affine
313+
# Rescale => Resample to resized voxels
314+
# Resize => Resample to new image dimensions
315+
update_zooms = not np.allclose(zooms, target_zooms, atol=atol_fine, rtol=0)
316+
rescale = not np.allclose(zooms, target_zooms, atol=atol_gross, rtol=0)
313317
resize = not np.all(shape == target_shape)
314-
if rescale or resize:
315-
if rescale:
318+
resample = rescale or resize
319+
if resample or update_zooms:
320+
# Use an affine with the corrected zooms, whether or not we resample
321+
if update_zooms:
316322
scale_factor = target_zooms / zooms
317-
target_affine[:3, :3] = reoriented.affine[:3, :3].dot(
318-
np.diag(scale_factor)
319-
)
323+
target_affine[:3, :3] = reoriented.affine[:3, :3] @ np.diag(scale_factor)
320324

321325
if resize:
322326
# The shift is applied after scaling.
323327
# Use a proportional shift to maintain relative position in dataset
324328
size_factor = target_span / (zooms * shape)
325329
# Use integer shifts to avoid unnecessary interpolation
326-
offset = (
327-
reoriented.affine[:3, 3] * size_factor - reoriented.affine[:3, 3]
328-
)
330+
offset = reoriented.affine[:3, 3] * size_factor - reoriented.affine[:3, 3]
329331
target_affine[:3, 3] = reoriented.affine[:3, 3] + offset.astype(int)
330332

331-
data = nli.resample_img(reoriented, target_affine, target_shape).dataobj
332-
conform_xfm = np.linalg.inv(reoriented.affine).dot(target_affine)
333+
conform_xfm = np.linalg.inv(reoriented.affine) @ target_affine
334+
335+
# Create new image
336+
data = reoriented.dataobj
337+
if resample:
338+
data = nli.resample_img(reoriented, target_affine, target_shape).dataobj
333339
reoriented = reoriented.__class__(data, target_affine, reoriented.header)
334340

335341
# Image may be reoriented, rescaled, and/or resized

niworkflows/interfaces/tests/test_images.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,35 @@ def test_IntraModalMerge(tmpdir, shape, mshape):
180180
merged_data = nb.load(merged).get_fdata(dtype="float32")
181181
new_mshape = (*mshape[:3], 2 if len(mshape) == 3 else mshape[3] * 2)
182182
assert merged_data.shape == new_mshape
183+
184+
185+
def test_conform_resize(tmpdir):
186+
fname = str(tmpdir / "test.nii")
187+
188+
random_data = np.random.random(size=(5, 5, 5))
189+
img = nb.Nifti1Image(random_data, np.eye(4))
190+
img.to_filename(fname)
191+
conform = pe.Node(im.Conform(), name="conform", base_dir=str(tmpdir))
192+
conform.inputs.in_file = fname
193+
conform.inputs.target_zooms = (1, 1, 1.5)
194+
conform.inputs.target_shape = (5, 5, 5)
195+
res = conform.run()
196+
197+
out_img = nb.load(res.outputs.out_file)
198+
assert out_img.header.get_zooms() == conform.inputs.target_zooms
199+
200+
201+
def test_conform_set_zooms(tmpdir):
202+
fname = str(tmpdir / "test.nii")
203+
204+
random_data = np.random.random(size=(5, 5, 5))
205+
img = nb.Nifti1Image(random_data, np.eye(4))
206+
img.to_filename(fname)
207+
conform = pe.Node(im.Conform(), name="conform", base_dir=str(tmpdir))
208+
conform.inputs.in_file = fname
209+
conform.inputs.target_zooms = (1, 1, 1.002)
210+
conform.inputs.target_shape = (5, 5, 5)
211+
res = conform.run()
212+
213+
out_img = nb.load(res.outputs.out_file)
214+
assert np.allclose(out_img.header.get_zooms(), conform.inputs.target_zooms)

0 commit comments

Comments
 (0)