def test_sample_domain_regular(): # Test 2D sampling shape = np.array((10, 10), dtype=np.int32) affine = np.eye(3) invalid_affine = np.eye(2) sigma = 0 dim = len(shape) n = shape[0] * shape[1] k = 2 # Verify exception is raised with invalid affine assert_raises(ValueError, sample_domain_regular, k, shape, invalid_affine, sigma) samples = sample_domain_regular(k, shape, affine, sigma) isamples = np.array(samples, dtype=np.int32) indices = (isamples[:, 0] * shape[1] + isamples[:, 1]) # Verify correct number of points sampled assert_array_equal(samples.shape, [n // k, dim]) # Verify all sampled points are different assert_equal(len(set(indices)), len(indices)) # Verify the sampling was regular at rate k assert_equal((indices % k).sum(), 0) # Test 3D sampling shape = np.array((5, 10, 10), dtype=np.int32) affine = np.eye(4) invalid_affine = np.eye(3) sigma = 0 dim = len(shape) n = shape[0] * shape[1] * shape[2] k = 10 # Verify exception is raised with invalid affine assert_raises(ValueError, sample_domain_regular, k, shape, invalid_affine, sigma) samples = sample_domain_regular(k, shape, affine, sigma) isamples = np.array(samples, dtype=np.int32) indices = (isamples[:, 0] * shape[1] * shape[2] + isamples[:, 1] * shape[2] + isamples[:, 2]) # Verify correct number of points sampled assert_array_equal(samples.shape, [n // k, dim]) # Verify all sampled points are different assert_equal(len(set(indices)), len(indices)) # Verify the sampling was regular at rate k assert_equal((indices % k).sum(), 0)
def test_mi_gradient_sparse(): # Test the gradient of mutual information h = 1e-5 for ttype in factors: transform = regtransforms[ttype] dim = ttype[1] if dim == 2: nslices = 1 interp_method = vf.interpolate_scalar_2d else: nslices = 45 interp_method = vf.interpolate_scalar_3d # 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 # Sample static domain k = 3 sigma = 0.25 seed = 1234 shape = np.array(static.shape, dtype=np.int32) samples = sample_domain_regular(k, shape, static_g2w, sigma, seed) samples = np.array(samples) samples = np.hstack((samples, np.ones(samples.shape[0])[:, None])) sp_to_static = np.linalg.inv(static_g2w) samples_static_grid = (sp_to_static.dot(samples.T).T)[..., :dim] intensities_static, inside = interp_method(static.astype(np.float32), samples_static_grid) intensities_static = np.array(intensities_static, dtype=np.float64) # 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 sp_to_moving = np.linalg.inv(moving_g2w) samples_moving_grid = (sp_to_moving.dot(samples.T).T)[..., :dim] intensities_moving, inside = interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) # 2. Update the joint distribution gradient (the derivative of each # histogram cell w.r.t. the transform parameters). This requires # to evaluate the gradient of the moving image at the sampling points theta = transform.get_identity_parameters().copy() spacing = np.ones(dim, dtype=np.float64) shape = np.array(static.shape, dtype=np.int32) mgrad, inside = vf.sparse_gradient(moving.astype(np.float32), sp_to_moving, spacing, samples[..., :dim]) metric.update_gradient_sparse(theta, transform, intensities_static, intensities_moving, samples[..., :dim], mgrad) # 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) sp_to_moving = np.linalg.inv(moving_g2w).dot(M) samples_moving_grid = (sp_to_moving.dot(samples.T).T)[..., :dim] intensities_moving, inside =\ interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) 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.9999)
def test_joint_pdf_gradients_sparse(): h = 1e-4 for ttype in factors: dim = ttype[1] if dim == 2: nslices = 1 interp_method = vf.interpolate_scalar_2d else: nslices = 45 interp_method = vf.interpolate_scalar_3d 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) # Sample the fixed-image domain k = 3 sigma = 0.25 seed = 1234 shape = np.array(static.shape, dtype=np.int32) samples = sample_domain_regular(k, shape, static_g2w, sigma, seed) samples = np.array(samples) samples = np.hstack((samples, np.ones(samples.shape[0])[:, None])) sp_to_static = np.linalg.inv(static_g2w) samples_static_grid = (sp_to_static.dot(samples.T).T)[..., :dim] intensities_static, inside = interp_method(static.astype(np.float32), samples_static_grid) # The routines in vector_fields operate, mostly, with float32 because # they were thought to be used for non-linear registration. We may need # to write some float64 counterparts for affine registration, where # memory is not so big issue intensities_static = np.array(intensities_static, dtype=np.float64) # Compute the gradient at theta with the implementation under test M = transform.param_to_matrix(theta) sp_to_moving = np.linalg.inv(moving_g2w).dot(M) samples_moving_grid = (sp_to_moving.dot(samples.T).T)[..., :dim] intensities_moving, inside = interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) # Get the joint distribution evaluated at theta J0 = np.copy(metric.joint) spacing = np.ones(dim + 1, dtype=np.float64) mgrad, inside = vf.sparse_gradient(moving.astype(np.float32), sp_to_moving, spacing, samples) metric.update_gradient_sparse(theta, transform, intensities_static, intensities_moving, samples[..., :dim], mgrad) # Get the gradient of the joint distribution w.r.t. the transform # parameters actual = np.copy(metric.joint_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 # Update the joint distribution with the warped moving image M = transform.param_to_matrix(dtheta) sp_to_moving = np.linalg.inv(moving_g2w).dot(M) samples_moving_grid = sp_to_moving.dot(samples.T).T intensities_moving, inside = \ interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) 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.99) assert(std_cosine < 0.15)
def test_mi_gradient_sparse(): # Test the gradient of mutual information h = 1e-5 for ttype in factors: transform = regtransforms[ttype] dim = ttype[1] if dim == 2: nslices = 1 interp_method = vf.interpolate_scalar_2d else: nslices = 45 interp_method = vf.interpolate_scalar_3d # 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 # Sample static domain k = 3 sigma = 0.25 seed = 1234 shape = np.array(static.shape, dtype=np.int32) samples = sample_domain_regular(k, shape, static_g2w, sigma, seed) samples = np.array(samples) samples = np.hstack((samples, np.ones(samples.shape[0])[:, None])) sp_to_static = np.linalg.inv(static_g2w) samples_static_grid = (sp_to_static.dot(samples.T).T)[..., :dim] intensities_static, inside = interp_method(static.astype(np.float32), samples_static_grid) intensities_static = np.array(intensities_static, dtype=np.float64) # 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 sp_to_moving = np.linalg.inv(moving_g2w) samples_moving_grid = (sp_to_moving.dot(samples.T).T)[..., :dim] intensities_moving, inside = interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) # 2. Update the joint distribution gradient (the derivative of each # histogram cell w.r.t. the transform parameters). This requires # to evaluate the gradient of the moving image at the sampling points theta = transform.get_identity_parameters().copy() spacing = np.ones(dim, dtype=np.float64) shape = np.array(static.shape, dtype=np.int32) mgrad, inside = vf.sparse_gradient(moving.astype(np.float32), sp_to_moving, spacing, samples[..., :dim]) metric.update_gradient_sparse(theta, transform, intensities_static, intensities_moving, samples[..., :dim], mgrad) # 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) sp_to_moving = np.linalg.inv(moving_g2w).dot(M) samples_moving_grid = (sp_to_moving.dot(samples.T).T)[..., :dim] intensities_moving, inside =\ interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) 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.9999)
def test_joint_pdf_gradients_sparse(): h = 1e-4 for ttype in factors: dim = ttype[1] if dim == 2: nslices = 1 interp_method = vf.interpolate_scalar_2d else: nslices = 45 interp_method = vf.interpolate_scalar_3d 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) # Sample the fixed-image domain k = 3 sigma = 0.25 seed = 1234 shape = np.array(static.shape, dtype=np.int32) samples = sample_domain_regular(k, shape, static_g2w, sigma, seed) samples = np.array(samples) samples = np.hstack((samples, np.ones(samples.shape[0])[:, None])) sp_to_static = np.linalg.inv(static_g2w) samples_static_grid = (sp_to_static.dot(samples.T).T)[..., :dim] intensities_static, inside = interp_method(static.astype(np.float32), samples_static_grid) # The routines in vector_fields operate, mostly, with float32 because # they were thought to be used for non-linear registration. We may need # to write some float64 counterparts for affine registration, where # memory is not so big issue intensities_static = np.array(intensities_static, dtype=np.float64) # Compute the gradient at theta with the implementation under test M = transform.param_to_matrix(theta) sp_to_moving = np.linalg.inv(moving_g2w).dot(M) samples_moving_grid = (sp_to_moving.dot(samples.T).T)[..., :dim] intensities_moving, inside = interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) # Get the joint distribution evaluated at theta J0 = np.copy(metric.joint) spacing = np.ones(dim + 1, dtype=np.float64) mgrad, inside = vf.sparse_gradient(moving.astype(np.float32), sp_to_moving, spacing, samples) metric.update_gradient_sparse(theta, transform, intensities_static, intensities_moving, samples[..., :dim], mgrad) # Get the gradient of the joint distribution w.r.t. the transform # parameters actual = np.copy(metric.joint_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 # Update the joint distribution with the warped moving image M = transform.param_to_matrix(dtheta) sp_to_moving = np.linalg.inv(moving_g2w).dot(M) samples_moving_grid = sp_to_moving.dot(samples.T).T intensities_moving, inside = \ interp_method(moving.astype(np.float32), samples_moving_grid) intensities_moving = np.array(intensities_moving, dtype=np.float64) metric.update_pdfs_sparse(intensities_static, intensities_moving) 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.99) assert (std_cosine < 0.15)