def test_diffeomorphic_map_simplification_2d(): r""" Create an invertible deformation field, and define a DiffeomorphicMap using different voxel-to-space transforms for domain, codomain, and reference discretizations, also use a non-identity pre-aligning matrix. Warp a circle using the diffeomorphic map to obtain the expected warped circle. Now simplify the DiffeomorphicMap and warp the same circle using this simplified map. Verify that the two warped circles are equal up to numerical precision. """ #create a simple affine transformation dom_shape = (64, 64) cod_shape = (80, 80) nr = dom_shape[0] nc = dom_shape[1] s = 1.1 t = 0.25 trans = np.array([[1, 0, -t*nr], [0, 1, -t*nc], [0, 0, 1]]) trans_inv = np.linalg.inv(trans) scale = np.array([[1*s, 0, 0], [0, 1*s, 0], [0, 0, 1]]) gt_affine = trans_inv.dot(scale.dot(trans)) # Create the invertible displacement fields and the circle radius = 16 circle = vfu.create_circle(cod_shape[0], cod_shape[1], radius) d, dinv = vfu.create_harmonic_fields_2d(dom_shape[0], dom_shape[1], 0.3, 6) #Define different voxel-to-space transforms for domain, codomain and #reference grid, also, use a non-identity pre-align transform D = gt_affine C = imwarp.mult_aff(gt_affine, gt_affine) R = np.eye(3) P = gt_affine #Create the original diffeomorphic map diff_map = imwarp.DiffeomorphicMap(2, dom_shape, R, dom_shape, D, cod_shape, C, P) diff_map.forward = np.array(d, dtype = floating) diff_map.backward = np.array(dinv, dtype = floating) #Warp the circle to obtain the expected image expected = diff_map.transform(circle, 'linear') #Simplify simplified = diff_map.get_simplified_transform() #warp the circle warped = simplified.transform(circle, 'linear') #verify that the simplified map is equivalent to the #original one assert_array_almost_equal(warped, expected) #And of course, it must be simpler... assert_equal(simplified.domain_affine, None) assert_equal(simplified.codomain_affine, None) assert_equal(simplified.disp_affine, None) assert_equal(simplified.domain_affine_inv, None) assert_equal(simplified.codomain_affine_inv, None) assert_equal(simplified.disp_affine_inv, None)
def load_mapping(fname): # create new instances mapping = imwarp.DiffeomorphicMap(None, []) affine = imaffine.AffineMap(None) data = read_hdf5(fname + '.h5') mapping.__dict__ = data.get(type(mapping).__name__) affine.__dict__ = data.get(type(affine).__name__) return mapping, affine
def read_morph(fname): if not fname.endswith('.h5'): fname += '.h5' morph_in = read_hdf5(fname) morph = dict() # create new instances morph['mapping'] = imwarp.DiffeomorphicMap(None, []) morph['mapping'].__dict__ = morph_in.get('mapping') morph['affine'] = imaffine.AffineMap(None) morph['affine'].__dict__ = morph_in.get('affine') morph['affine_reg'] = morph_in.get('affine_reg') morph['domain_shape'] = morph_in.get('domain_shape') return morph
def test_diffeomorphic_map_2d(): r""" Creates a random displacement field that exactly maps pixels from an input image to an output image. First a discrete random assignment between the images is generated, then each pair of mapped points are transformed to the physical space by assigning a pair of arbitrary, fixed affine matrices to input and output images, and finaly the difference between their positions is taken as the displacement vector. The resulting displacement, although operating in physical space, maps the points exactly (up to numerical precision). """ np.random.seed(2022966) domain_shape = (10, 10) codomain_shape = (10, 10) #create a simple affine transformation nr = domain_shape[0] nc = domain_shape[1] s = 1.1 t = 0.25 trans = np.array([[1, 0, -t*nr], [0, 1, -t*nc], [0, 0, 1]]) trans_inv = np.linalg.inv(trans) scale = np.array([[1*s, 0, 0], [0, 1*s, 0], [0, 0, 1]]) gt_affine = trans_inv.dot(scale.dot(trans)) #create the random displacement field domain_affine = gt_affine codomain_affine = gt_affine disp, assign = vfu.create_random_displacement_2d( np.array(domain_shape, dtype=np.int32), domain_affine,np.array(codomain_shape, dtype=np.int32), codomain_affine) disp = np.array(disp, dtype=floating) assign = np.array(assign) #create a random image (with decimal digits) to warp moving_image = np.ndarray(codomain_shape, dtype=floating) ns = np.size(moving_image) moving_image[...] = np.random.randint(0, 10, ns).reshape(codomain_shape) #set boundary values to zero so we don't test wrong interpolation due #to floating point precision moving_image[0,:] = 0 moving_image[-1,:] = 0 moving_image[:,0] = 0 moving_image[:,-1] = 0 #warp the moving image using the (exact) assignments expected = moving_image[(assign[...,0], assign[...,1])] #warp using a DiffeomorphicMap instance diff_map = imwarp.DiffeomorphicMap(2, domain_shape, domain_affine, domain_shape, domain_affine, codomain_shape, codomain_affine, None) diff_map.forward = disp #Verify that the transform method accepts different image types (note that #the actual image contained integer values, we don't want to test rounding) for type in [floating, np.float64, np.int64, np.int32]: moving_image = moving_image.astype(type) #warp using linear interpolation warped = diff_map.transform(moving_image, 'linear') #compare the images (the linear interpolation may introduce slight #precision errors) assert_array_almost_equal(warped, expected, decimal=5) #Now test the nearest neighbor interpolation warped = diff_map.transform(moving_image, 'nearest') #compare the images (now we dont have to worry about precision, #it is n.n.) assert_array_almost_equal(warped, expected) #verify the is_inverse flag inv = diff_map.inverse() warped = inv.transform_inverse(moving_image, 'linear') assert_array_almost_equal(warped, expected, decimal=5) warped = inv.transform_inverse(moving_image, 'nearest') assert_array_almost_equal(warped, expected) #Now test the inverse functionality diff_map = imwarp.DiffeomorphicMap(2, codomain_shape, codomain_affine, codomain_shape, codomain_affine, domain_shape, domain_affine, None) diff_map.backward = disp for type in [floating, np.float64, np.int64, np.int32]: moving_image = moving_image.astype(type) #warp using linear interpolation warped = diff_map.transform_inverse(moving_image, 'linear') #compare the images (the linear interpolation may introduce slight #precision errors) assert_array_almost_equal(warped, expected, decimal=5) #Now test the nearest neighbor interpolation warped = diff_map.transform_inverse(moving_image, 'nearest') #compare the images (now we don't have to worry about precision, #it is nearest neighbour) assert_array_almost_equal(warped, expected) #Verify that DiffeomorphicMap raises the appropriate exceptions when #the sampling information is undefined diff_map = imwarp.DiffeomorphicMap(2, domain_shape, domain_affine, domain_shape, domain_affine, codomain_shape, codomain_affine, None) diff_map.forward = disp diff_map.domain_shape = None #If we don't provide the sampling info, it should try to use the map's #info, but it's None... assert_raises(ValueError, diff_map.transform, moving_image, 'linear') #Same test for diff_map.transform_inverse diff_map = imwarp.DiffeomorphicMap(2, domain_shape, domain_affine, domain_shape, domain_affine, codomain_shape, codomain_affine, None) diff_map.forward = disp diff_map.codomain_shape = None #If we don't provide the sampling info, it should try to use the map's #info, but it's None... assert_raises(ValueError, diff_map.transform_inverse, moving_image, 'linear') #We must provide, at least, the reference grid shape assert_raises(ValueError, imwarp.DiffeomorphicMap, 2, None)
def test_invert_vector_field(shape): r""" Inverts a synthetic, analytically invertible, displacement field """ ndim = len(shape) if ndim == 3: ns = shape[0] nr = shape[1] nc = shape[2] # Create an arbitrary image-to-space transform # Select an arbitrary rotation axis axis = np.array([2.0, 0.5, 1.0]) t = 2.5 # translation factor trans = np.array([ [1, 0, 0, -t * ns], [0, 1, 0, -t * nr], [0, 0, 1, -t * nc], [0, 0, 0, 1], ]) dipy_create_func = vfu.create_harmonic_fields_3d dipy_reorient_func = vfu.reorient_vector_field_3d dipy_invert_func = vfu.invert_vector_field_fixed_point_3d elif ndim == 2: nr = shape[0] nc = shape[1] # Create an arbitrary image-to-space transform t = 2.5 # translation factor trans = np.array([[1, 0, -t * nr], [0, 1, -t * nc], [0, 0, 1]]) dipy_create_func = vfu.create_harmonic_fields_2d dipy_reorient_func = vfu.reorient_vector_field_2d dipy_invert_func = vfu.invert_vector_field_fixed_point_2d trans_inv = np.linalg.inv(trans) d, _ = dipy_create_func(*shape, 0.2, 8) d = np.asarray(d).astype(floating) for theta in [-1 * np.pi / 5.0, 0.0, np.pi / 5.0]: # rotation angle for s in [0.5, 1.0, 2.0]: # scale if ndim == 3: rot = np.zeros(shape=(4, 4)) rot[:3, :3] = geometry.rodrigues_axis_rotation(axis, theta) rot[3, 3] = 1.0 scale = np.array([ [1 * s, 0, 0, 0], [0, 1 * s, 0, 0], [0, 0, 1 * s, 0], [0, 0, 0, 1], ]) elif ndim == 2: ct = np.cos(theta) st = np.sin(theta) rot = np.array([[ct, -st, 0], [st, ct, 0], [0, 0, 1]]) scale = np.array([[1 * s, 0, 0], [0, 1 * s, 0], [0, 0, 1]]) gt_affine = trans_inv.dot(scale.dot(rot.dot(trans))) gt_affine_inv = np.linalg.inv(gt_affine) dcopy = np.copy(d) dcopyd = cupy.asarray(dcopy) gt_affined = cupy.asarray(gt_affine) gt_affine_invd = cupy.asarray(gt_affine_inv) # make sure the field remains invertible after the re-mapping dipy_reorient_func(dcopy, gt_affine) # TODO: can't do in-place computation unless out= is supplied and # dcopy has the dimensions axis first instead of last dcopyd = reorient_vector_field(dcopyd, gt_affined) cupy.testing.assert_array_almost_equal(dcopyd, dcopy, decimal=4) # Note: the spacings are used just to check convergence, so they # don't need to be very accurate. Here we are passing (0.5 * s) to # force the algorithm to make more iterations: in ANTS, there is a # hard-coded bound on the maximum residual, that's why we cannot # force more iteration by changing the parameters. # We will investigate this issue with more detail in the future. if False: from cupyx.time import repeat perf = repeat( invert_vector_field_fixed_point, ( dcopyd, gt_affine_invd, cupy.asarray([s, s, s]) * 0.5, 40, 1e-7, ), n_warmup=20, n_repeat=80, ) print(perf) perf = repeat( dipy_invert_func, ( dcopy, gt_affine_inv, np.asarray([s, s, s]) * 0.5, 40, 1e-7, ), n_warmup=0, n_repeat=8, ) print(perf) # if False: # from pyvolplot import volshow # from matplotlib import pyplot as plt # inv_approx, q, norms, tmp1, tmp2, epsilon, maxlen = vfu.invert_vector_field_fixed_point_3d_debug( # dcopy, gt_affine_inv, np.array([s, s, s]) * 0.5, max_iter=1, tol=1e-7 # ) # inv_approxd, qd, normsd, tmp1d, tmp2d, epsilond, maxlend = invert_vector_field_fixed_point( # dcopyd, gt_affine_invd, cupy.asarray([s, s, s]) * 0.5, max_iter=1, tol=1e-7 # ) inv_approxd = invert_vector_field_fixed_point( dcopyd, gt_affine_invd, cupy.asarray([s, s, s]) * 0.5, 40, 1e-7) if False: inv_approx = dipy_invert_func(dcopy, gt_affine_inv, np.array([s, s, s]) * 0.5, 40, 1e-7) cupy.testing.assert_allclose(inv_approx, inv_approxd, rtol=1e-2, atol=1e-2) # TODO: use GPU-based imwarp here once implemented mapping = imwarp.DiffeomorphicMap(ndim, shape, gt_affine) mapping.forward = dcopy mapping.backward = inv_approxd.get() residual, stats = mapping.compute_inversion_error() assert_almost_equal(stats[1], 0, decimal=3) assert_almost_equal(stats[2], 0, decimal=3)