Skip to content

Commit 1f70c7b

Browse files
committed
enh: function that calculates the new reference grid
1 parent 0d97d9e commit 1f70c7b

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

sdcflows/utils/tools.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,71 @@
2323
"""Image processing tools."""
2424

2525

26+
def deoblique_and_zooms(in_reference, oblique, factor=4, padding=1):
27+
"""
28+
Generate a sampling reference aligned with in_reference fully covering oblique.
29+
30+
Parameters
31+
----------
32+
in_reference : :obj:`~nibabel.spatialimages.SpatialImage`
33+
The sampling reference.
34+
oblique : :obj:`~nibabel.spatialimages.SpatialImage`
35+
The rotated coordinate system whose extent should be fully covered by the
36+
generated reference.
37+
factor : :obj:`int`
38+
A factor to increase the resolution of the generated reference.
39+
padding : :obj:`int`
40+
Number of additional voxels around the most extreme positions of the projection of
41+
oblique on to the reference.
42+
43+
"""
44+
from itertools import product
45+
import numpy as np
46+
from nibabel.affines import apply_affine, rescale_affine
47+
48+
# Reference space metadata
49+
hdr = in_reference.header.copy()
50+
affine = in_reference.affine
51+
ref_shape = np.array(in_reference.shape[:3])
52+
ref_zooms = np.array(hdr.get_zooms()[:3])
53+
_, scode = in_reference.get_sform(coded=True)
54+
_, qcode = in_reference.get_qform(coded=True)
55+
56+
# Calculate the 8 most extreme coordinates of oblique in in_reference space
57+
extent = apply_affine(
58+
oblique.affine,
59+
np.array(list(product((0, 1), repeat=3))) * (ref_shape - 1),
60+
)
61+
extent_ijk = apply_affine(np.linalg.inv(affine), extent)
62+
63+
if isinstance(padding, int):
64+
padding = tuple([padding] * 3)
65+
66+
underflow = np.clip(extent_ijk.min(0) - padding, None, 0).astype(int)
67+
overflow = np.ceil(np.clip(extent_ijk.max(0) + padding + 1 - ref_shape, 0, None)).astype(int)
68+
if np.any(underflow < 0) or np.any(overflow > 0):
69+
# Add under/overflow voxels
70+
ref_shape += np.abs(underflow) + overflow
71+
# Consistently update origin
72+
affine[:-1, -1] = apply_affine(affine, underflow)
73+
74+
# Make grid denser
75+
if abs(1.0 - factor) > 1e-5:
76+
new_shape = np.rint(ref_shape * factor)
77+
affine = rescale_affine(affine, ref_shape, ref_zooms / factor, new_shape)
78+
ref_shape = new_shape
79+
80+
# Generate new reference
81+
hdr.set_sform(affine, scode)
82+
hdr.set_qform(affine, qcode)
83+
84+
return in_reference.__class__(
85+
np.zeros(ref_shape.astype(int), dtype=hdr.get_data_dtype()),
86+
affine,
87+
hdr,
88+
)
89+
90+
2691
def resample_to_zooms(in_file, zooms, order=3, prefilter=True):
2792
"""Resample the input data to a new grid with the requested zooms."""
2893
from pathlib import Path

0 commit comments

Comments
 (0)