Esempio n. 1
0
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)
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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)
Esempio n. 5
0
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)