def compute_demons_step(self, forward_step=True): r"""Demons step for EM metric Parameters ---------- forward_step : boolean if True, computes the Demons step in the forward direction (warping the moving towards the static image). If False, computes the backward step (warping the static image to the moving image) Returns ------- displacement : array, shape (R, C, 2) or (S, R, C, 3) the Demons step """ sigma_reg_2 = np.sum(self.static_spacing**2)/self.dim if forward_step: gradient = self.gradient_static delta_field = self.static_image - self.movingq_means_field sigma_sq_field = self.movingq_sigma_sq_field else: gradient = self.gradient_moving delta_field = self.moving_image - self.staticq_means_field sigma_sq_field = self.staticq_sigma_sq_field if self.dim == 2: step, self.energy = em.compute_em_demons_step_2d(delta_field, sigma_sq_field, gradient, sigma_reg_2, None) else: step, self.energy = em.compute_em_demons_step_3d(delta_field, sigma_sq_field, gradient, sigma_reg_2, None) for i in range(self.dim): step[..., i] = ndimage.filters.gaussian_filter(step[..., i], self.smooth) return step
def test_compute_em_demons_step_2d(): r""" Compares the output of the demons step in 2d against an analytical step. The fixed image is given by $F(x) = \frac{1}{2}||x - c_f||^2$, the moving image is given by $G(x) = \frac{1}{2}||x - c_g||^2$, $x, c_f, c_g \in R^{2}$ References ---------- [Vercauteren09] Vercauteren, T., Pennec, X., Perchant, A., & Ayache, N. (2009). Diffeomorphic demons: efficient non-parametric image registration. NeuroImage, 45(1 Suppl), S61-72. doi:10.1016/j.neuroimage.2008.10.040 """ # Select arbitrary images' shape (same shape for both images) sh = (30, 20) # Select arbitrary centers c_f = np.asarray(sh) / 2 c_g = c_f + 0.5 # Compute the identity vector field I(x) = x in R^2 x_0 = np.asarray(range(sh[0])) x_1 = np.asarray(range(sh[1])) X = np.ndarray(sh + (2, ), dtype=np.float64) O = np.ones(sh) X[..., 0] = x_0[:, None] * O X[..., 1] = x_1[None, :] * O # Compute the gradient fields of F and G grad_F = X - c_f grad_G = X - c_g # The squared norm of grad_G to be used later sq_norm_grad_G = np.sum(grad_G**2, -1) # Compute F and G F = 0.5 * np.sum(grad_F**2, -1) G = 0.5 * sq_norm_grad_G delta_field = G - F # Now select an arbitrary parameter for # $\sigma_x$ (eq 4 in [Vercauteren09]) sigma_x_sq = 1.5 # Set arbitrary values for $\sigma_i$ (eq. 4 in [Vercauteren09]) # The original Demons algorithm used simply |F(x) - G(x)| as an # estimator, so let's use it as well sigma_i_sq = (F - G)**2 # Select some pixels to have special values np.random.seed(1346491) random_labels = np.random.randint(0, 5, sh[0] * sh[1]) random_labels = random_labels.reshape(sh) # this label is used to set sigma_i_sq == 0 below random_labels[sigma_i_sq == 0] = 2 # this label is used to set gradient == 0 below random_labels[sq_norm_grad_G == 0] = 2 expected = np.zeros_like(grad_G) # Pixels with sigma_i_sq = inf sigma_i_sq[random_labels == 0] = np.inf expected[random_labels == 0, ...] = 0 # Pixels with gradient!=0 and sigma_i_sq=0 sqnrm = sq_norm_grad_G[random_labels == 1] sigma_i_sq[random_labels == 1] = 0 expected[random_labels == 1, 0] = (delta_field[random_labels == 1] * grad_G[random_labels == 1, 0] / sqnrm) expected[random_labels == 1, 1] = (delta_field[random_labels == 1] * grad_G[random_labels == 1, 1] / sqnrm) # Pixels with gradient=0 and sigma_i_sq=0 sigma_i_sq[random_labels == 2] = 0 grad_G[random_labels == 2, ...] = 0 expected[random_labels == 2, ...] = 0 # Pixels with gradient=0 and sigma_i_sq!=0 grad_G[random_labels == 3, ...] = 0 # Directly compute the demons step according to eq. 4 in [Vercauteren09] num = (sigma_x_sq * (F - G))[random_labels >= 3] den = (sigma_x_sq * sq_norm_grad_G + sigma_i_sq)[random_labels >= 3] # This is $J^{P}$ in eq. 4 [Vercauteren09] expected[random_labels >= 3] = -1 * np.array(grad_G[random_labels >= 3]) expected[random_labels >= 3, ...] *= (num / den)[..., None] # Now compute it using the implementation under test actual = np.empty_like(expected, dtype=floating) em.compute_em_demons_step_2d(np.array(delta_field, dtype=floating), np.array(sigma_i_sq, dtype=floating), np.array(grad_G, dtype=floating), sigma_x_sq, actual) # Test sigma_i_sq == inf try: assert_array_almost_equal(actual[random_labels == 0], expected[random_labels == 0]) except AssertionError: raise AssertionError("Failed for sigma_i_sq == inf") # Test sigma_i_sq == 0 and gradient != 0 try: assert_array_almost_equal(actual[random_labels == 1], expected[random_labels == 1]) except AssertionError: raise AssertionError("Failed for sigma_i_sq == 0 and gradient != 0") # Test sigma_i_sq == 0 and gradient == 0 try: assert_array_almost_equal(actual[random_labels == 2], expected[random_labels == 2]) except AssertionError: raise AssertionError("Failed for sigma_i_sq == 0 and gradient == 0") # Test sigma_i_sq != 0 and gradient == 0 try: assert_array_almost_equal(actual[random_labels == 3], expected[random_labels == 3]) except AssertionError: raise AssertionError("Failed for sigma_i_sq != 0 and gradient == 0 ") # Test sigma_i_sq != 0 and gradient != 0 try: assert_array_almost_equal(actual[random_labels == 4], expected[random_labels == 4]) except AssertionError: raise AssertionError("Failed for sigma_i_sq != 0 and gradient != 0")
def test_compute_em_demons_step_2d(): r""" Compares the output of the demons step in 2d against an analytical step. The fixed image is given by $F(x) = \frac{1}{2}||x - c_f||^2$, the moving image is given by $G(x) = \frac{1}{2}||x - c_g||^2$, $x, c_f, c_g \in R^{2}$ References ---------- [Vercauteren09] Vercauteren, T., Pennec, X., Perchant, A., & Ayache, N. (2009). Diffeomorphic demons: efficient non-parametric image registration. NeuroImage, 45(1 Suppl), S61-72. doi:10.1016/j.neuroimage.2008.10.040 """ #Select arbitrary images' shape (same shape for both images) sh = (30, 20) #Select arbitrary centers c_f = np.asarray(sh)/2 c_g = c_f + 0.5 #Compute the identity vector field I(x) = x in R^2 x_0 = np.asarray(range(sh[0])) x_1 = np.asarray(range(sh[1])) X = np.ndarray(sh + (2,), dtype = np.float64) O = np.ones(sh) X[...,0]= x_0[:, None] * O X[...,1]= x_1[None, :] * O #Compute the gradient fields of F and G grad_F = X - c_f grad_G = X - c_g #The squared norm of grad_G to be used later sq_norm_grad_G = np.sum(grad_G**2,-1) #Compute F and G F = 0.5*np.sum(grad_F**2,-1) G = 0.5*sq_norm_grad_G delta_field = G - F #Now select an arbitrary parameter for $\sigma_x$ (eq 4 in [Vercauteren09]) sigma_x_sq = 1.5 #Set arbitrary values for $\sigma_i$ (eq. 4 in [Vercauteren09]) #The original Demons algorithm used simply |F(x) - G(x)| as an #estimator, so let's use it as well sigma_i_sq = (F - G)**2 #Select some pixels to have special values np.random.seed(1346491) random_labels = np.random.randint(0, 5, sh[0]*sh[1]) random_labels = random_labels.reshape(sh) random_labels[sigma_i_sq==0] = 2 #this label is used to set sigma_i_sq == 0 below random_labels[sq_norm_grad_G==0] = 2 #this label is used to set gradient == 0 below expected = np.zeros_like(grad_G) #Pixels with sigma_i_sq = inf sigma_i_sq[random_labels == 0] = np.inf expected[random_labels == 0, ...] = 0 #Pixels with gradient!=0 and sigma_i_sq=0 sqnrm = sq_norm_grad_G[random_labels == 1] sigma_i_sq[random_labels == 1] = 0 expected[random_labels == 1, 0] = delta_field[random_labels == 1]*grad_G[random_labels == 1, 0]/sqnrm expected[random_labels == 1, 1] = delta_field[random_labels == 1]*grad_G[random_labels == 1, 1]/sqnrm #Pixels with gradient=0 and sigma_i_sq=0 sigma_i_sq[random_labels == 2] = 0 grad_G[random_labels == 2, ...] = 0 expected[random_labels == 2, ...] = 0 #Pixels with gradient=0 and sigma_i_sq!=0 grad_G[random_labels == 3, ...] = 0 #Directly compute the demons step according to eq. 4 in [Vercauteren09] num = (sigma_x_sq * (F - G))[random_labels >= 3] den = (sigma_x_sq * sq_norm_grad_G + sigma_i_sq)[random_labels >= 3] expected[random_labels >= 3] = -1 * np.array(grad_G[random_labels >= 3]) #This is $J^{P}$ in eq. 4 [Vercauteren09] expected[random_labels >= 3,...] *= (num / den)[..., None] #Now compute it using the implementation under test actual = np.empty_like(expected, dtype=floating) em.compute_em_demons_step_2d(np.array(delta_field, dtype=floating), np.array(sigma_i_sq, dtype=floating), np.array(grad_G, dtype=floating), sigma_x_sq, actual) #Test sigma_i_sq == inf try: assert_array_almost_equal(actual[random_labels==0], expected[random_labels==0]) except AssertionError: raise AssertionError("Failed for sigma_i_sq == inf") #Test sigma_i_sq == 0 and gradient != 0 try: assert_array_almost_equal(actual[random_labels==1], expected[random_labels==1]) except AssertionError: raise AssertionError("Failed for sigma_i_sq == 0 and gradient != 0") #Test sigma_i_sq == 0 and gradient == 0 try: assert_array_almost_equal(actual[random_labels==2], expected[random_labels==2]) except AssertionError: raise AssertionError("Failed for sigma_i_sq == 0 and gradient == 0") #Test sigma_i_sq != 0 and gradient == 0 try: assert_array_almost_equal(actual[random_labels==3], expected[random_labels==3]) except AssertionError: raise AssertionError("Failed for sigma_i_sq != 0 and gradient == 0 ") #Test sigma_i_sq != 0 and gradient != 0 try: assert_array_almost_equal(actual[random_labels==4], expected[random_labels==4]) except AssertionError: raise AssertionError("Failed for sigma_i_sq != 0 and gradient != 0")