@@ -254,3 +254,45 @@ def dot_reduce(*args):
254254 ... arg[N-2].dot(arg[N-1])))...``
255255 """
256256 return reduce (lambda x , y : np .dot (y , x ), args [::- 1 ])
257+
258+
259+ def voxel_sizes (affine ):
260+ r""" Return voxel size for each input axis given `affine`
261+
262+ The `affine` is the mapping between array (voxel) coordinates and mm
263+ (world) coordinates.
264+
265+ The voxel size for the first voxel (array) axis is the distance moved in
266+ world coordinates when moving one unit along the first voxel (array) axis.
267+ This is the distance between the world coordinate of voxel (0, 0, 0) and
268+ the world coordinate of voxel (1, 0, 0). The world coordinate vector of
269+ voxel coordinate vector (0, 0, 0) is given by ``v0 = affine.dot((0, 0, 0,
270+ 1)[:3]``. The world coordinate vector of voxel vector (1, 0, 0) is
271+ ``v1_ax1 = affine.dot((1, 0, 0, 1))[:3]``. The final 1 in the voxel
272+ vectors and the ``[:3]`` at the end are because the affine works on
273+ homogenous coodinates. The translations part of the affine is ``trans =
274+ affine[:3, 3]``, and the rotations, zooms and shearing part of the affine
275+ is ``rzs = affine[:3, :3]``. Because of the final 1 in the input voxel
276+ vector, ``v0 == rzs.dot((0, 0, 0)) + trans``, and ``v1_ax1 == rzs.dot((1,
277+ 0, 0)) + trans``, and the difference vector is ``rzs.dot((0, 0, 0)) -
278+ rzs.dot((1, 0, 0)) == rzs.dot((1, 0, 0)) == rzs[:, 0]``. The distance
279+ vectors in world coordinates between (0, 0, 0) and (1, 0, 0), (0, 1, 0),
280+ (0, 0, 1) are given by ``rzs.dot(np.eye(3)) = rzs``. The voxel sizes are
281+ the Euclidean lengths of the distance vectors. So, the voxel sizes are
282+ the Euclidean lengths of the columns of the affine (excluding the last row
283+ and column of the affine).
284+
285+ Parameters
286+ ----------
287+ affine : 2D array-like
288+ Affine transformation array. Usually shape (4, 4), but can be any 2D
289+ array.
290+
291+ Returns
292+ -------
293+ vox_sizes : 1D array
294+ Voxel sizes for each input axis of affine. Usually 1D array length 3,
295+ but in general has length (N-1) where input `affine` is shape (M, N).
296+ """
297+ top_left = affine [:- 1 , :- 1 ]
298+ return np .sqrt (np .sum (top_left ** 2 , axis = 0 ))
0 commit comments