Skip to content

Commit

Permalink
RF: change to mapped_voxels as vox2out_vox input
Browse files Browse the repository at this point in the history
Also refactor tests to be a little less repetive.
  • Loading branch information
matthew-brett committed Aug 25, 2014
1 parent c2b0ff6 commit 8bf7bf5
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 73 deletions.
32 changes: 21 additions & 11 deletions nibabel/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
A voxel space can be expressed by a shape implying an array, where the axes are
the axes of the array.
A mapped voxel space (mapped voxels) is either:
* an image, with attributes ``shape`` (the voxel space) and ``affine`` (the
mapping), or
* a length 2 sequence with the same information (shape, affine).
"""

from itertools import product
Expand All @@ -21,12 +27,11 @@
from .affines import apply_affine


def vox2out_vox(in_shape, in_affine, voxel_sizes=None):
""" output-aligned shape, affine for input voxels implied by `in_shape`
The input (voxel) space is given by `in_shape`
def vox2out_vox(mapped_voxels, voxel_sizes=None):
""" output-aligned shape, affine for input implied by `mapped_voxels`
The mapping between input space and output space is `in_affine`
The input (voxel) space, and the affine mapping to output space, are given
in `mapped_voxels`.
The output space is implied by the affine, we don't need to know what that
is, we just return something with the same (implied) output space.
Expand All @@ -39,10 +44,11 @@ def vox2out_vox(in_shape, in_affine, voxel_sizes=None):
Parameters
----------
in_shape : sequence
shape of implied input image voxel block. Up to length 3.
in_affine : (4, 4) array-like
affine mapping voxel coordinates in `in_shape` to output coordinates.
mapped_voxels : object or length 2 sequence
If object, has attributes ``shape`` giving input voxel shape, and
``affine`` giving mapping of input voxels to output space. If length 2
sequence, elements are (shape, affine) with same meaning as above. The
affine is a (4, 4) array-like.
voxel_sizes : None or sequence
Gives the diagonal entries of `output_affine` (except the trailing 1
for the homogenous coordinates) (``output_affine == np.diag(voxel_sizes
Expand All @@ -53,14 +59,18 @@ def vox2out_vox(in_shape, in_affine, voxel_sizes=None):
output_shape : sequence
Shape of output image that has voxel axes aligned to original image
output space axes, and encloses all the voxel data from the original
image implied by `in_shape`.
image implied by input shape.
output_affine : (4, 4) array
Affine of output image that has voxel axes aligned to the output axes
implied by `in_affine`. Top-left 3 x 3 part of affine is diagonal with
implied by input affine. Top-left 3 x 3 part of affine is diagonal with
all positive entries. The entries come from `voxel_sizes` if
specified, or are all 1. If the image is < 3D, then the missing
dimensions will have a 1 in the matching diagonal.
"""
try:
in_shape, in_affine = mapped_voxels.shape, mapped_voxels.affine
except AttributeError:
in_shape, in_affine = mapped_voxels
n_axes = len(in_shape)
if n_axes > 3:
raise ValueError('This function can only deal with 3D images')
Expand Down
119 changes: 57 additions & 62 deletions nibabel/tests/test_spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import numpy.linalg as npl

from ..spaces import vox2out_vox, slice2volume
from ..affines import apply_affine
from ..affines import apply_affine, from_matvec
from ..nifti1 import Nifti1Image
from ..eulerangles import euler2mat


Expand Down Expand Up @@ -36,71 +37,65 @@ def assert_all_in(in_shape, in_affine, out_shape, out_affine):

def test_vox2out_vox():
# Test world space bounding box
shape, aff = vox2out_vox((2, 3, 4), np.eye(4))
# Test basic case, identity, no voxel sizes passed
shape, aff = vox2out_vox(((2, 3, 4), np.eye(4)))
assert_array_equal(shape, (2, 3, 4))
assert_true(isinstance(shape, tuple))
assert_true(isinstance(shape[0], int))
assert_array_equal(aff, np.eye(4))
assert_all_in((2, 3, 4), np.eye(4), shape, aff)
shape, aff = vox2out_vox((2, 3, 4), np.diag([-1, 1, 1, 1]))
assert_array_equal(shape, (2, 3, 4))
assert_array_equal(aff, [[1, 0, 0, -1], # axis reversed -> -ve offset
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]])
assert_all_in((2, 3, 4), np.diag([-1, 1, 1, 1]), shape, aff)
# zooms for affine > 1 -> larger grid with default 1mm output voxels
shape, aff = vox2out_vox((2, 3, 4), np.diag([4, 5, 6, 1]))
assert_array_equal(shape, (5, 11, 19))
assert_array_equal(aff, np.eye(4))
assert_all_in((2, 3, 4), np.diag([4, 5, 6, 1]), shape, aff)
# set output voxels to be same size as input. back to original shape
shape, aff = vox2out_vox((2, 3, 4), np.diag([4, 5, 6, 1]), (4, 5, 6))
assert_array_equal(shape, (2, 3, 4))
assert_array_equal(aff, np.diag([4, 5, 6, 1]))
assert_all_in((2, 3, 4), np.diag([4, 5, 6, 1]), shape, aff)
# zero point preserved
in_aff = [[1, 0, 0, 1], [0, 1, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]]
shape, aff = vox2out_vox((2, 3, 4), in_aff)
assert_array_equal(shape, (2, 3, 4))
assert_array_equal(aff, in_aff)
assert_all_in((2, 3, 4), in_aff, shape, aff)
in_aff = [[1, 0, 0, -1], [0, 1, 0, -2], [0, 0, 1, -3], [0, 0, 0, 1]]
shape, aff = vox2out_vox((2, 3, 4), in_aff)
assert_array_equal(shape, (2, 3, 4))
assert_array_equal(aff, in_aff)
assert_all_in((2, 3, 4), in_aff, shape, aff)
# rotation around third axis
in_aff = np.eye(4)
in_aff[:3, :3] = euler2mat(np.pi / 4)
shape, aff = vox2out_vox((2, 3, 4), in_aff)
# x diff, y diff now 3 cos pi / 4 == 2.12, ceil to 3, add 1
assert_array_equal(shape, (4, 4, 4))
# most negative x now 2 cos pi / 4
assert_almost_equal(aff, [[1, 0, 0, -2 * np.cos(np.pi / 4)],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]])
assert_all_in((2, 3, 4), in_aff, shape, aff)
# Some affines as input to the tests
trans_123 = [[1, 0, 0, 1], [0, 1, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]]
trans_m123 = [[1, 0, 0, -1], [0, 1, 0, -2], [0, 0, 1, -3], [0, 0, 0, 1]]
rot_3 = from_matvec(euler2mat(np.pi / 4), [0, 0, 0])
for in_shape, in_aff, vox, out_shape, out_aff in (
# Identity
((2, 3, 4), np.eye(4), None, (2, 3, 4), np.eye(4)),
# Flip first axis
((2, 3, 4), np.diag([-1, 1, 1, 1]), None,
(2, 3, 4), [[1, 0, 0, -1], # axis reversed -> -ve offset
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]),
# zooms for affine > 1 -> larger grid with default 1mm output voxels
((2, 3, 4), np.diag([4, 5, 6, 1]), None,
(5, 11, 19), np.eye(4)),
# set output voxels to be same size as input. back to original shape
((2, 3, 4), np.diag([4, 5, 6, 1]), (4, 5, 6),
(2, 3, 4), np.diag([4, 5, 6, 1])),
# Translation preserved in output
((2, 3, 4), trans_123, None,
(2, 3, 4), trans_123),
((2, 3, 4), trans_m123, None,
(2, 3, 4), trans_m123),
# rotation around 3rd axis
((2, 3, 4), rot_3, None,
# x diff, y diff now 3 cos pi / 4 == 2.12, ceil to 3, add 1
# most negative x now 2 cos pi / 4
(4, 4, 4), [[1, 0, 0, -2 * np.cos(np.pi / 4)],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]),
# Less than 3 axes
((2, 3), np.eye(4), None,
(2, 3), np.eye(4)),
((2,), np.eye(4), None,
(2,), np.eye(4)),
# Number of voxel sizes matches length
((2, 3), np.diag([4, 5, 6, 1]), (4, 5),
(2, 3), np.diag([4, 5, 1, 1])),
):
img = Nifti1Image(np.ones(in_shape), in_aff)
for input in ((in_shape, in_aff), img):
shape, aff = vox2out_vox(input, vox)
assert_all_in(in_shape, in_aff, shape, aff)
assert_equal(shape, out_shape)
assert_almost_equal(aff, out_aff)
assert_true(isinstance(shape, tuple))
assert_true(isinstance(shape[0], int))
# Enforce number of axes
assert_raises(ValueError, vox2out_vox, (2, 3, 4, 5), np.eye(4))
assert_raises(ValueError, vox2out_vox, (2, 3, 4, 5, 6), np.eye(4))
# Less than 3 is OK
shape, aff = vox2out_vox((2, 3), np.eye(4))
assert_array_equal(shape, (2, 3))
assert_array_equal(aff, np.eye(4))
assert_all_in((2, 3), np.eye(4), shape, aff)
shape, aff = vox2out_vox((2,), np.eye(4))
assert_array_equal(shape, (2,))
assert_array_equal(aff, np.eye(4))
assert_all_in((2,), np.eye(4), shape, aff)
# Number of voxel sizes matches length
shape, aff = vox2out_vox((2, 3), np.diag([4, 5, 6, 1]), (4, 5))
assert_array_equal(shape, (2, 3))
assert_array_equal(aff, np.diag([4, 5, 1, 1]))
assert_raises(ValueError, vox2out_vox, ((2, 3, 4, 5), np.eye(4)))
assert_raises(ValueError, vox2out_vox, ((2, 3, 4, 5, 6), np.eye(4)))
# Voxel sizes must be positive
assert_raises(ValueError, vox2out_vox, (2, 3, 4), np.eye(4), [-1, 1, 1])
assert_raises(ValueError, vox2out_vox, (2, 3, 4), np.eye(4), [1, 0, 1])
assert_raises(ValueError, vox2out_vox, ((2, 3, 4), np.eye(4), [-1, 1, 1]))
assert_raises(ValueError, vox2out_vox, ((2, 3, 4), np.eye(4), [1, 0, 1]))


def test_slice2volume():
Expand Down

0 comments on commit 8bf7bf5

Please sign in to comment.