Beispiel #1
0
def test_mi_gradient_dense():
    # Test the gradient of mutual information
    h = 1e-5
    for ttype in factors:
        transform = regtransforms[ttype]
        dim = ttype[1]
        if dim == 2:
            nslices = 1
            warp_method = vf.warp_2d_affine
        else:
            nslices = 45
            warp_method = vf.warp_3d_affine
        # Get data (pair of images related to each other by an known transform)
        factor = factors[ttype]
        static, moving, static_g2w, moving_g2w, smask, mmask, M = \
            setup_random_transform(transform, factor, nslices, 5.0)
        smask = None
        mmask = None

        # Prepare a MattesBase instance
        # The computation of the metric is done in 3 steps:
        # 1.Compute the joint distribution
        # 2.Compute the gradient of the joint distribution
        # 3.Compute the metric's value and gradient using results from 1 and 2
        metric = MattesBase(32)
        metric.setup(static, moving, smask, mmask)

        # 1. Update the joint distribution
        metric.update_pdfs_dense(static.astype(np.float64),
                                 moving.astype(np.float64))

        # 2. Update the joint distribution gradient (the derivative of each
        # histogram cell w.r.t. the transform parameters). This requires
        # among other things, the spatial gradient of the moving image.
        theta = transform.get_identity_parameters().copy()
        grid_to_space = np.eye(dim + 1)
        spacing = np.ones(dim, dtype=np.float64)
        shape = np.array(static.shape, dtype=np.int32)
        mgrad, inside = vf.gradient(moving.astype(np.float32), moving_g2w,
                                    spacing, shape, grid_to_space)
        metric.update_gradient_dense(theta, transform,
                                     static.astype(np.float64),
                                     moving.astype(np.float64),
                                     grid_to_space, mgrad, smask, mmask)

        # 3. Update the metric (in this case, the Mutual Information) and its
        # gradient, which is computed from the joint density and its gradient
        metric.update_mi_metric(update_gradient=True)

        # Now we can extract the value and gradient of the metric
        # This is the gradient according to the implementation under test
        val0 = metric.metric_val
        actual = np.copy(metric.metric_grad)

        # Compute the gradient using finite-diferences
        n = transform.get_number_of_parameters()
        expected = np.empty_like(actual)
        for i in range(n):
            dtheta = theta.copy()
            dtheta[i] += h

            M = transform.param_to_matrix(dtheta)
            shape = np.array(static.shape, dtype=np.int32)
            warped = np.array(warp_method(moving.astype(np.float32), shape, M))
            metric.update_pdfs_dense(static.astype(np.float64),
                                     warped.astype(np.float64))
            metric.update_mi_metric(update_gradient=False)
            val1 = metric.metric_val
            expected[i] = (val1 - val0) / h

        dp = expected.dot(actual)
        enorm = np.linalg.norm(expected)
        anorm = np.linalg.norm(actual)
        nprod = dp / (enorm * anorm)
        assert(nprod >= 0.999)
Beispiel #2
0
def test_joint_pdf_gradients_dense():
    # Compare the analytical and numerical (finite differences) gradient of
    # the joint distribution (i.e. derivatives of each histogram cell) w.r.t.
    # the transform parameters. Since the histograms are discrete partitions
    # of the image intensities, the finite difference approximation is
    # normally not very close to the analytical derivatives. Other sources of
    # error are the interpolation used when transforming the images and the
    # boundary intensities introduced when interpolating outside of the image
    # (i.e. some "zeros" are introduced at the boundary which affect the
    # numerical derivatives but is not taken into account by the analytical
    # derivatives). Thus, we need to relax the verification. Instead of
    # looking for the analytical and numerical gradients to be very close to
    # each other, we will verify that they approximately point in the same
    # direction by testing if the angle they form is close to zero.
    h = 1e-4

    # Make sure dictionary entries are processed in the same order regardless
    # of the platform. Otherwise any random numbers drawn within the loop
    # would make the test non-deterministic even if we fix the seed before
    # the loop. Right now, this test does not draw any samples, but we still
    # sort the entries to prevent future related failures.
    for ttype in sorted(factors):
        dim = ttype[1]
        if dim == 2:
            nslices = 1
            transform_method = vf.transform_2d_affine
        else:
            nslices = 45
            transform_method = vf.transform_3d_affine

        transform = regtransforms[ttype]
        factor = factors[ttype]
        theta = transform.get_identity_parameters()

        static, moving, static_g2w, moving_g2w, smask, mmask, M = \
            setup_random_transform(transform, factor, nslices, 5.0)
        parzen_hist = ParzenJointHistogram(32)
        parzen_hist.setup(static, moving, smask, mmask)

        # Compute the gradient at theta with the implementation under test
        M = transform.param_to_matrix(theta)
        shape = np.array(static.shape, dtype=np.int32)

        moved = transform_method(moving.astype(np.float32), shape, M)
        moved = np.array(moved)
        parzen_hist.update_pdfs_dense(static.astype(np.float64),
                                      moved.astype(np.float64))
        # Get the joint distribution evaluated at theta
        J0 = np.copy(parzen_hist.joint)
        grid_to_space = np.eye(dim + 1)
        spacing = np.ones(dim, dtype=np.float64)
        mgrad, inside = vf.gradient(moving.astype(np.float32), moving_g2w,
                                    spacing, shape, grid_to_space)
        id = transform.get_identity_parameters()
        parzen_hist.update_gradient_dense(id, transform,
                                          static.astype(np.float64),
                                          moved.astype(np.float64),
                                          grid_to_space, mgrad, smask, mmask)
        actual = np.copy(parzen_hist.joint_grad)
        # Now we have the gradient of the joint distribution w.r.t. the
        # transform parameters

        # Compute the gradient using finite-diferences
        n = transform.get_number_of_parameters()
        expected = np.empty_like(actual)
        for i in range(n):
            dtheta = theta.copy()
            dtheta[i] += h
            # Update the joint distribution with the transformed moving image
            M = transform.param_to_matrix(dtheta)
            shape = np.array(static.shape, dtype=np.int32)
            moved = transform_method(moving.astype(np.float32), shape, M)
            moved = np.array(moved)
            parzen_hist.update_pdfs_dense(static.astype(np.float64),
                                          moved.astype(np.float64))
            J1 = np.copy(parzen_hist.joint)
            expected[..., i] = (J1 - J0) / h

        # Dot product and norms of gradients of each joint histogram cell
        # i.e. the derivatives of each cell w.r.t. all parameters
        P = (expected * actual).sum(2)
        enorms = np.sqrt((expected**2).sum(2))
        anorms = np.sqrt((actual**2).sum(2))
        prodnorms = enorms * anorms
        # Cosine of angle between the expected and actual gradients.
        # Exclude very small gradients
        P[prodnorms > 1e-6] /= (prodnorms[prodnorms > 1e-6])
        P[prodnorms <= 1e-6] = 0
        # Verify that a large proportion of the gradients point almost in
        # the same direction. Disregard very small gradients
        mean_cosine = P[P != 0].mean()
        std_cosine = P[P != 0].std()
        assert (mean_cosine > 0.9)
        assert (std_cosine < 0.25)
Beispiel #3
0
def test_joint_pdf_gradients_dense():
    # Compare the analytical and numerical (finite differences) gradient of the
    # joint distribution (i.e. derivatives of each histogram cell) w.r.t. the
    # transform parameters. Since the histograms are discrete partitions of the
    # image intensities, the finite difference approximation is normally not
    # very close to the analytical derivatives. Other sources of error are the
    # interpolation used when warping the images and the boundary intensities
    # introduced when interpolating outside of the image (i.e. some "zeros" are
    # introduced at the boundary which affect the numerical derivatives but is
    # not taken into account by the analytical derivatives). Thus, we need to
    # relax the verification. Instead of looking for the analytical and
    # numerical gradients to be very close to each other, we will verify that
    # they approximately point in the same direction by testing if the angle
    # they form is close to zero.
    h = 1e-4
    for ttype in factors:
        dim = ttype[1]
        if dim == 2:
            nslices = 1
            warp_method = vf.warp_2d_affine
        else:
            nslices = 45
            warp_method = vf.warp_3d_affine

        transform = regtransforms[ttype]
        factor = factors[ttype]
        theta = transform.get_identity_parameters()

        static, moving, static_g2w, moving_g2w, smask, mmask, M = \
            setup_random_transform(transform, factor, nslices, 5.0)
        metric = MattesBase(32)
        metric.setup(static, moving, smask, mmask)

        # Compute the gradient at theta with the implementation under test
        M = transform.param_to_matrix(theta)
        shape = np.array(static.shape, dtype=np.int32)

        warped = warp_method(moving.astype(np.float32), shape, M)
        warped = np.array(warped)
        metric.update_pdfs_dense(static.astype(np.float64),
                                 warped.astype(np.float64))
        # Get the joint distribution evaluated at theta
        J0 = np.copy(metric.joint)
        grid_to_space = np.eye(dim + 1)
        spacing = np.ones(dim, dtype=np.float64)
        mgrad, inside = vf.gradient(moving.astype(np.float32), moving_g2w,
                                    spacing, shape, grid_to_space)
        id = transform.get_identity_parameters()
        metric.update_gradient_dense(id, transform, static.astype(np.float64),
                                     warped.astype(np.float64), grid_to_space,
                                     mgrad, smask, mmask)
        actual = np.copy(metric.joint_grad)
        # Now we have the gradient of the joint distribution w.r.t. the
        # transform parameters

        # Compute the gradient using finite-diferences
        n = transform.get_number_of_parameters()
        expected = np.empty_like(actual)
        for i in range(n):
            dtheta = theta.copy()
            dtheta[i] += h
            # Update the joint distribution with the warped moving image
            M = transform.param_to_matrix(dtheta)
            shape = np.array(static.shape, dtype=np.int32)
            warped = warp_method(moving.astype(np.float32), shape, M)
            warped = np.array(warped)
            metric.update_pdfs_dense(static.astype(np.float64),
                                     warped.astype(np.float64))
            J1 = np.copy(metric.joint)
            expected[..., i] = (J1 - J0) / h

        # Dot product and norms of gradients of each joint histogram cell
        # i.e. the derivatives of each cell w.r.t. all parameters
        P = (expected * actual).sum(2)
        enorms = np.sqrt((expected ** 2).sum(2))
        anorms = np.sqrt((actual ** 2).sum(2))
        prodnorms = enorms * anorms
        # Cosine of angle between the expected and actual gradients.
        # Exclude very small gradients
        P[prodnorms > 1e-6] /= (prodnorms[prodnorms > 1e-6])
        P[prodnorms <= 1e-6] = 0
        # Verify that a large proportion of the gradients point almost in
        # the same direction. Disregard very small gradients
        mean_cosine = P[P != 0].mean()
        std_cosine = P[P != 0].std()
        assert(mean_cosine > 0.9)
        assert(std_cosine < 0.25)
Beispiel #4
0
def test_gradient_3d():
    np.random.seed(3921116)
    shape = (25, 32, 15)
    # Create grid coordinates
    x_0 = np.asarray(range(shape[0]))
    x_1 = np.asarray(range(shape[1]))
    x_2 = np.asarray(range(shape[2]))
    X = np.zeros(shape + (4, ), dtype=np.float64)
    O = np.ones(shape)
    X[..., 0] = x_0[:, None, None] * O
    X[..., 1] = x_1[None, :, None] * O
    X[..., 2] = x_2[None, None, :] * O
    X[..., 3] = 1

    transform = regtransforms[("RIGID", 3)]
    theta = np.array([0.1, 0.05, 0.12, -12.0, -15.5, -7.2])
    T = transform.param_to_matrix(theta)

    TX = X.dot(T.T)
    # Eval an arbitrary (known) function at TX
    # f(x, y, z) = ax^2 + by^2 + cz^2 + dxy + exz + fyz
    # df/dx = 2ax + dy + ez
    # df/dy = 2by + dx + fz
    # df/dz = 2cz + ex + fy
    a, b, c = 2e-3, 3e-3, 1e-3
    d, e, f = 1e-3, 2e-3, 3e-3
    img = (a * TX[..., 0]**2 + b * TX[..., 1]**2 + c * TX[..., 2]**2 +
           d * TX[..., 0] * TX[..., 1] + e * TX[..., 0] * TX[..., 2] +
           f * TX[..., 1] * TX[..., 2])

    img = img.astype(floating)
    # Test sparse gradient: choose some sample points (in space)
    sample = sample_domain_regular(100, np.array(shape, dtype=np.int32), T)
    sample = np.array(sample)
    # Compute the analytical gradient at all points
    expected = np.empty((sample.shape[0], 3), dtype=floating)
    expected[...,
             0] = (2 * a * sample[:, 0] + d * sample[:, 1] + e * sample[:, 2])
    expected[...,
             1] = (2 * b * sample[:, 1] + d * sample[:, 0] + f * sample[:, 2])
    expected[...,
             2] = (2 * c * sample[:, 2] + e * sample[:, 0] + f * sample[:, 1])
    # Get the numerical gradient with the implementation under test
    sp_to_grid = np.linalg.inv(T)
    img_spacing = np.ones(3)
    actual, inside = vfu.sparse_gradient(img, sp_to_grid, img_spacing, sample)

    img_d = cupy.asarray(img)
    img_spacing_d = cupy.asarray(img_spacing)
    sp_to_grid_d = cupy.asarray(sp_to_grid)
    sample_d = cupy.asarray(sample)
    actual_gpu, inside_gpu = sparse_gradient(img_d, sp_to_grid_d,
                                             img_spacing_d, sample_d.T)
    atol = rtol = 1e-5
    cupy.testing.assert_allclose(
        actual * inside[..., np.newaxis],
        actual_gpu * inside_gpu[..., np.newaxis],
        atol=atol,
        rtol=rtol,
    )
    cupy.testing.assert_array_equal(inside, inside_gpu)

    # TODO: test invalid inputs
    # # Verify exception is raised when passing invalid affine or spacings
    # invalid_affine = np.eye(3)
    # invalid_spacings = np.ones(2)
    # assert_raises(ValueError, vfu.sparse_gradient, img, invalid_affine,
    #               img_spacing, sample)
    # assert_raises(ValueError, vfu.sparse_gradient, img, sp_to_grid,
    #               invalid_spacings, sample)

    # Test dense gradient
    # Compute the analytical gradient at all points
    expected = np.empty(shape + (3, ), dtype=floating)
    expected[..., 0] = 2 * a * TX[..., 0] + d * TX[..., 1] + e * TX[..., 2]
    expected[..., 1] = 2 * b * TX[..., 1] + d * TX[..., 0] + f * TX[..., 2]
    expected[..., 2] = 2 * c * TX[..., 2] + e * TX[..., 0] + f * TX[..., 1]
    # Get the numerical gradient with the implementation under test
    sp_to_grid = np.linalg.inv(T)
    img_spacing = np.ones(3)
    actual, inside = vfu.gradient(img, sp_to_grid, img_spacing, shape, T)

    sp_to_grid_d = cupy.asarray(sp_to_grid)
    img_spacing_d = cupy.asarray(img_spacing)
    T_d = cupy.asarray(T)
    actual_gpu, inside_gpu = gradient(img_d, sp_to_grid_d, img_spacing_d,
                                      shape, T_d)

    atol = rtol = 1e-5
    cupy.testing.assert_allclose(
        actual * inside[..., np.newaxis],
        actual_gpu * inside_gpu[..., np.newaxis],
        atol=atol,
        rtol=rtol,
    )
    cupy.testing.assert_array_equal(inside, inside_gpu)
Beispiel #5
0
def test_gradient_2d():
    np.random.seed(3921116)
    sh = (25, 32)
    # Create grid coordinates
    x_0 = np.arange(sh[0])
    x_1 = np.arange(sh[1])
    X = np.empty(sh + (3, ), dtype=np.float64)
    O = np.ones(sh)
    X[..., 0] = x_0[:, None] * O
    X[..., 1] = x_1[None, :] * O
    X[..., 2] = 1

    transform = regtransforms[("RIGID", 2)]
    theta = np.array([0.1, 5.0, 2.5])
    T = transform.param_to_matrix(theta)
    TX = X.dot(T.T)
    # Eval an arbitrary (known) function at TX
    # f(x, y) = ax^2 + bxy + cy^{2}
    # df/dx = 2ax + by
    # df/dy = 2cy + bx
    a = 2e-3
    b = 5e-3
    c = 7e-3
    img = (a * TX[..., 0]**2 + b * TX[..., 0] * TX[..., 1] + c * TX[..., 1]**2)
    img = img.astype(floating)
    # img is an image sampled at X with grid-to-space transform T

    # Test sparse gradient: choose some sample points (in space)
    sample = sample_domain_regular(20, np.array(sh, dtype=np.int32), T)
    sample = np.array(sample)
    # Compute the analytical gradient at all points
    expected = np.empty((sample.shape[0], 2), dtype=floating)
    expected[..., 0] = 2 * a * sample[:, 0] + b * sample[:, 1]
    expected[..., 1] = 2 * c * sample[:, 1] + b * sample[:, 0]
    # Get the numerical gradient with the implementation under test
    sp_to_grid = np.linalg.inv(T)
    img_spacing = np.ones(2)

    img_d = cupy.asarray(img)
    img_spacing_d = cupy.asarray(img_spacing)
    sp_to_grid_d = cupy.asarray(sp_to_grid)
    sample_d = cupy.asarray(sample)

    actual, inside = vfu.sparse_gradient(img, sp_to_grid, img_spacing, sample)
    actual_gpu, inside_gpu = sparse_gradient(img_d, sp_to_grid_d,
                                             img_spacing_d, sample_d.T)
    atol = rtol = 1e-5
    cupy.testing.assert_allclose(
        actual * inside[..., np.newaxis],
        actual_gpu * inside_gpu[..., np.newaxis],
        atol=atol,
        rtol=rtol,
    )
    cupy.testing.assert_array_equal(inside, inside_gpu)

    # TODO: verify exceptions
    # # Verify exception is raised when passing invalid affine or spacings
    # invalid_affine = np.eye(2)
    # invalid_spacings = np.ones(1)
    # assert_raises(ValueError, vfu.sparse_gradient, img, invalid_affine,
    #               img_spacing, sample)
    # assert_raises(ValueError, vfu.sparse_gradient, img, sp_to_grid,
    #               invalid_spacings, sample)

    # Test dense gradient
    # Compute the analytical gradient at all points
    expected = np.empty(sh + (2, ), dtype=floating)
    expected[..., 0] = 2 * a * TX[..., 0] + b * TX[..., 1]
    expected[..., 1] = 2 * c * TX[..., 1] + b * TX[..., 0]
    # Get the numerical gradient with the implementation under test
    sp_to_grid = np.linalg.inv(T)
    img_spacing = np.ones(2)

    actual, inside = vfu.gradient(img, sp_to_grid, img_spacing, sh, T)
    sp_to_grid_d = cupy.asarray(sp_to_grid)
    img_spacing_d = cupy.asarray(img_spacing)
    T_d = cupy.asarray(T)
    actual_gpu, inside_gpu = gradient(img_d, sp_to_grid_d, img_spacing_d, sh,
                                      T_d)

    atol = rtol = 1e-5
    cupy.testing.assert_allclose(
        actual * inside[..., np.newaxis],
        actual_gpu * inside_gpu[..., np.newaxis],
        atol=atol,
        rtol=rtol,
    )
    cupy.testing.assert_array_equal(inside, inside_gpu)
Beispiel #6
0
static = np.array(static).astype(np.float64)
moving = np.array(moving).astype(np.float64)

import numpy.linalg as npl

moving_world2grid = npl.inv(moving_grid2world)

from dipy.align.imwarp import get_direction_and_spacings

dim = len(static.shape)
_, moving_spacing = get_direction_and_spacings(moving_grid2world, dim)

from dipy.align.vector_fields import gradient

mgrad, _ = gradient(moving, moving_world2grid, moving_spacing, static.shape,
                    static_grid2world)

np.save('sl_aff_par_grad.npy', mgrad)

# out = np.asarray(out, dtype=np.float64)
# out = 255 * (out - out.min()) / (out.max() - out.min())

# slice_indices = np.array(out.shape) // 2
# axial = np.asarray(out[:, :, slice_indices[2]]).astype(np.uint8)
# coronal = np.asarray(out[:, slice_indices[1], :]).astype(np.uint8)
# sagittal = np.asarray(out[slice_indices[0], :, :]).astype(np.uint8)

# import matplotlib.pyplot as plt
# from xvfbwrapper import Xvfb
# with Xvfb() as xvfb:
# 	fig, ax = plt.subplots(3, 3)
Beispiel #7
0
def test_mi_gradient_dense():
    # Test the gradient of mutual information
    h = 1e-5
    for ttype in factors:
        transform = regtransforms[ttype]
        dim = ttype[1]
        if dim == 2:
            nslices = 1
            warp_method = vf.warp_2d_affine
        else:
            nslices = 45
            warp_method = vf.warp_3d_affine
        # Get data (pair of images related to each other by an known transform)
        factor = factors[ttype]
        static, moving, static_g2w, moving_g2w, smask, mmask, M = \
            setup_random_transform(transform, factor, nslices, 5.0)
        smask = None
        mmask = None

        # Prepare a MattesBase instance
        # The computation of the metric is done in 3 steps:
        # 1.Compute the joint distribution
        # 2.Compute the gradient of the joint distribution
        # 3.Compute the metric's value and gradient using results from 1 and 2
        metric = MattesBase(32)
        metric.setup(static, moving, smask, mmask)

        # 1. Update the joint distribution
        metric.update_pdfs_dense(static.astype(np.float64),
                                 moving.astype(np.float64))

        # 2. Update the joint distribution gradient (the derivative of each
        # histogram cell w.r.t. the transform parameters). This requires
        # among other things, the spatial gradient of the moving image.
        theta = transform.get_identity_parameters().copy()
        grid_to_space = np.eye(dim + 1)
        spacing = np.ones(dim, dtype=np.float64)
        shape = np.array(static.shape, dtype=np.int32)
        mgrad, inside = vf.gradient(moving.astype(np.float32), moving_g2w,
                                    spacing, shape, grid_to_space)
        metric.update_gradient_dense(theta, transform,
                                     static.astype(np.float64),
                                     moving.astype(np.float64), grid_to_space,
                                     mgrad, smask, mmask)

        # 3. Update the metric (in this case, the Mutual Information) and its
        # gradient, which is computed from the joint density and its gradient
        metric.update_mi_metric(update_gradient=True)

        # Now we can extract the value and gradient of the metric
        # This is the gradient according to the implementation under test
        val0 = metric.metric_val
        actual = np.copy(metric.metric_grad)

        # Compute the gradient using finite-diferences
        n = transform.get_number_of_parameters()
        expected = np.empty_like(actual)
        for i in range(n):
            dtheta = theta.copy()
            dtheta[i] += h

            M = transform.param_to_matrix(dtheta)
            shape = np.array(static.shape, dtype=np.int32)
            warped = np.array(warp_method(moving.astype(np.float32), shape, M))
            metric.update_pdfs_dense(static.astype(np.float64),
                                     warped.astype(np.float64))
            metric.update_mi_metric(update_gradient=False)
            val1 = metric.metric_val
            expected[i] = (val1 - val0) / h

        dp = expected.dot(actual)
        enorm = np.linalg.norm(expected)
        anorm = np.linalg.norm(actual)
        nprod = dp / (enorm * anorm)
        assert (nprod >= 0.999)