def test_EMMetric_image_dynamics(): np.random.seed(7181309) metric = EMMetric(2) target_shape = (10, 10) #create a random image image = np.ndarray(target_shape, dtype=floating) image[...] = np.random.randint(0, 10, np.size(image)).reshape(tuple(target_shape)) #compute the expected binary mask expected = (image > 0).astype(np.int32) metric.use_static_image_dynamics(image, None) assert_array_equal(expected, metric.static_image_mask) metric.use_moving_image_dynamics(image, None) assert_array_equal(expected, metric.moving_image_mask)
def test_EMMetric_image_dynamics(): np.random.seed(7181309) metric = EMMetric(2) target_shape = (10, 10) # create a random image image = np.ndarray(target_shape, dtype=floating) image[...] = np.random.randint(0, 10, np.size(image)).reshape(tuple(target_shape)) # compute the expected binary mask expected = (image > 0).astype(np.int32) metric.use_static_image_dynamics(image, None) assert_array_equal(expected, metric.static_image_mask) metric.use_moving_image_dynamics(image, None) assert_array_equal(expected, metric.moving_image_mask)
def run(self, static_image_files, moving_image_files, prealign_file='', inv_static=False, level_iters=[10, 10, 5], metric="cc", mopt_sigma_diff=2.0, mopt_radius=4, mopt_smooth=0.0, mopt_inner_iter=0.0, mopt_q_levels=256, mopt_double_gradient=True, mopt_step_type='', step_length=0.25, ss_sigma_factor=0.2, opt_tol=1e-5, inv_iter=20, inv_tol=1e-3, out_dir='', out_warped='warped_moved.nii.gz', out_inv_static='inc_static.nii.gz', out_field='displacement_field.nii.gz'): """ Parameters ---------- static_image_files : string Path of the static image file. moving_image_files : string Path to the moving image file. prealign_file : string, optional The text file containing pre alignment information via an affine matrix. inv_static : boolean, optional Apply the inverse mapping to the static image (default 'False'). level_iters : variable int, optional The number of iterations at each level of the gaussian pyramid. By default, a 3-level scale space with iterations sequence equal to [10, 10, 5] will be used. The 0-th level corresponds to the finest resolution. metric : string, optional The metric to be used (Default cc, 'Cross Correlation metric'). metric available: cc (Cross Correlation), ssd (Sum Squared Difference), em (Expectation-Maximization). mopt_sigma_diff : float, optional Metric option applied on Cross correlation (CC). The standard deviation of the Gaussian smoothing kernel to be applied to the update field at each iteration (default 2.0) mopt_radius : int, optional Metric option applied on Cross correlation (CC). the radius of the squared (cubic) neighborhood at each voxel to be considered to compute the cross correlation. (default 4) mopt_smooth : float, optional Metric option applied on Sum Squared Difference (SSD) and Expectation Maximization (EM). Smoothness parameter, the larger the value the smoother the deformation field. (default 1.0 for EM, 4.0 for SSD) mopt_inner_iter : int, optional Metric option applied on Sum Squared Difference (SSD) and Expectation Maximization (EM). This is number of iterations to be performed at each level of the multi-resolution Gauss-Seidel optimization algorithm (this is not the number of steps per Gaussian Pyramid level, that parameter must be set for the optimizer, not the metric). Default 5 for EM, 10 for SSD. mopt_q_levels : int, optional Metric option applied on Expectation Maximization (EM). Number of quantization levels (Default: 256 for EM) mopt_double_gradient : bool, optional Metric option applied on Expectation Maximization (EM). if True, the gradient of the expected static image under the moving modality will be added to the gradient of the moving image, similarly, the gradient of the expected moving image under the static modality will be added to the gradient of the static image. mopt_step_type : string, optional Metric option applied on Sum Squared Difference (SSD) and Expectation Maximization (EM). The optimization schedule to be used in the multi-resolution Gauss-Seidel optimization algorithm (not used if Demons Step is selected). Possible value: ('gauss_newton', 'demons'). default: 'gauss_newton' for EM, 'demons' for SSD. step_length : float, optional the length of the maximum displacement vector of the update displacement field at each iteration. ss_sigma_factor : float, optional parameter of the scale-space smoothing kernel. For example, the std. dev. of the kernel will be factor*(2^i) in the isotropic case where i = 0, 1, ..., n_scales is the scale. opt_tol : float, optional the optimization will stop when the estimated derivative of the energy profile w.r.t. time falls below this threshold. inv_iter : int, optional the number of iterations to be performed by the displacement field inversion algorithm. inv_tol : float, optional the displacement field inversion algorithm will stop iterating when the inversion error falls below this threshold. out_dir : string, optional Directory to save the transformed files (default ''). out_warped : string, optional Name of the warped file. (default 'warped_moved.nii.gz'). out_inv_static : string, optional Name of the file to save the static image after applying the inverse mapping (default 'inv_static.nii.gz'). out_field : string, optional Name of the file to save the diffeomorphic map. (default 'displacement_field.nii.gz') """ io_it = self.get_io_iterator() metric = metric.lower() if metric not in ['ssd', 'cc', 'em']: raise ValueError("Invalid similarity metric: Please" " provide a valid metric like 'ssd', 'cc', 'em'") logging.info("Starting Diffeomorphic Registration") logging.info('Using {0} Metric'.format(metric.upper())) # Init parameter if they are not setup init_param = { 'ssd': { 'mopt_smooth': 4.0, 'mopt_inner_iter': 10, 'mopt_step_type': 'demons' }, 'em': { 'mopt_smooth': 1.0, 'mopt_inner_iter': 5, 'mopt_step_type': 'gauss_newton' } } mopt_smooth = mopt_smooth or init_param[metric]['mopt_smooth'] mopt_inner_iter = mopt_inner_iter or \ init_param[metric]['mopt_inner_iter'] mopt_step_type = mopt_step_type or \ init_param[metric]['mopt_step_type'] for (static_file, moving_file, owarped_file, oinv_static_file, omap_file) in io_it: logging.info('Loading static file {0}'.format(static_file)) logging.info('Loading moving file {0}'.format(moving_file)) # Loading the image data from the input files into object. static_image, static_grid2world = load_nifti(static_file) moving_image, moving_grid2world = load_nifti(moving_file) # Sanity check for the input image dimensions. check_dimensions(static_image, moving_image) # Loading the affine matrix. prealign = np.loadtxt(prealign_file) if prealign_file else None l_metric = { "ssd": SSDMetric(static_image.ndim, smooth=mopt_smooth, inner_iter=mopt_inner_iter, step_type=mopt_step_type), "cc": CCMetric(static_image.ndim, sigma_diff=mopt_sigma_diff, radius=mopt_radius), "em": EMMetric(static_image.ndim, smooth=mopt_smooth, inner_iter=mopt_inner_iter, step_type=mopt_step_type, q_levels=mopt_q_levels, double_gradient=mopt_double_gradient) } current_metric = l_metric.get(metric.lower()) sdr = SymmetricDiffeomorphicRegistration( metric=current_metric, level_iters=level_iters, step_length=step_length, ss_sigma_factor=ss_sigma_factor, opt_tol=opt_tol, inv_iter=inv_iter, inv_tol=inv_tol) mapping = sdr.optimize(static_image, moving_image, static_grid2world, moving_grid2world, prealign) mapping_data = np.array([mapping.forward.T, mapping.backward.T]).T warped_moving = mapping.transform(moving_image) # Saving logging.info('Saving warped {0}'.format(owarped_file)) save_nifti(owarped_file, warped_moving, static_grid2world) logging.info('Saving Diffeomorphic map {0}'.format(omap_file)) save_nifti(omap_file, mapping_data, mapping.codomain_world2grid)
def test_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 = (20, 10) #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_F = np.sum(grad_F**2,-1) sq_norm_grad_G = np.sum(grad_G**2,-1) #Compute F and G F = 0.5 * sq_norm_grad_F G = 0.5 * sq_norm_grad_G #Create an instance of EMMetric metric = EMMetric(2) metric.static_spacing = np.array([1.2, 1.2]) #The $\sigma_x$ (eq. 4 in [Vercauteren09]) parameter is computed in ANTS #based on the image's spacing sigma_x_sq = np.sum(metric.static_spacing**2)/metric.dim #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 #Set the properties relevant to the demons methods metric.smooth = 3.0 metric.gradient_static = np.array(grad_F, dtype = floating) metric.gradient_moving = np.array(grad_G, dtype = floating) metric.static_image = np.array(F, dtype = floating) metric.moving_image = np.array(G, dtype = floating) metric.staticq_means_field = np.array(F, dtype = floating) metric.staticq_sigma_sq_field = np.array(sigma_i_sq, dtype = floating) metric.movingq_means_field = np.array(G, dtype = floating) metric.movingq_sigma_sq_field = np.array(sigma_i_sq, dtype = floating) #compute the step using the implementation under test actual_forward = metric.compute_demons_step(True) actual_backward = metric.compute_demons_step(False) #Now directly compute the demons steps according to eq 4 in [Vercauteren09] num_fwd = sigma_x_sq * (G - F) den_fwd = sigma_x_sq * sq_norm_grad_F + sigma_i_sq expected_fwd = -1 * np.array(grad_F) #This is $J^{P}$ in eq. 4 [Vercauteren09] expected_fwd[..., 0] *= num_fwd / den_fwd expected_fwd[..., 1] *= num_fwd / den_fwd #apply Gaussian smoothing expected_fwd[..., 0] = ndimage.filters.gaussian_filter(expected_fwd[..., 0], 3.0) expected_fwd[..., 1] = ndimage.filters.gaussian_filter(expected_fwd[..., 1], 3.0) num_bwd = sigma_x_sq * (F - G) den_bwd = sigma_x_sq * sq_norm_grad_G + sigma_i_sq expected_bwd = -1 * np.array(grad_G) #This is $J^{P}$ in eq. 4 [Vercauteren09] expected_bwd[..., 0] *= num_bwd / den_bwd expected_bwd[..., 1] *= num_bwd / den_bwd #apply Gaussian smoothing expected_bwd[..., 0] = ndimage.filters.gaussian_filter(expected_bwd[..., 0], 3.0) expected_bwd[..., 1] = ndimage.filters.gaussian_filter(expected_bwd[..., 1], 3.0) assert_array_almost_equal(actual_forward, expected_fwd) assert_array_almost_equal(actual_backward, expected_bwd)
def test_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 = (20, 10) # 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_F = np.sum(grad_F**2, -1) sq_norm_grad_G = np.sum(grad_G**2, -1) # Compute F and G F = 0.5 * sq_norm_grad_F G = 0.5 * sq_norm_grad_G # Create an instance of EMMetric metric = EMMetric(2) metric.static_spacing = np.array([1.2, 1.2]) # The $\sigma_x$ (eq. 4 in [Vercauteren09]) parameter is computed in ANTS # based on the image's spacing sigma_x_sq = np.sum(metric.static_spacing**2) / metric.dim # 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 # Set the properties relevant to the demons methods metric.smooth = 3.0 metric.gradient_static = np.array(grad_F, dtype=floating) metric.gradient_moving = np.array(grad_G, dtype=floating) metric.static_image = np.array(F, dtype=floating) metric.moving_image = np.array(G, dtype=floating) metric.staticq_means_field = np.array(F, dtype=floating) metric.staticq_sigma_sq_field = np.array(sigma_i_sq, dtype=floating) metric.movingq_means_field = np.array(G, dtype=floating) metric.movingq_sigma_sq_field = np.array(sigma_i_sq, dtype=floating) # compute the step using the implementation under test actual_forward = metric.compute_demons_step(True) actual_backward = metric.compute_demons_step(False) # Now directly compute the demons steps according to eq 4 in # [Vercauteren09] num_fwd = sigma_x_sq * (G - F) den_fwd = sigma_x_sq * sq_norm_grad_F + sigma_i_sq # This is $J^{P}$ in eq. 4 [Vercauteren09] expected_fwd = -1 * np.array(grad_F) expected_fwd[..., 0] *= num_fwd / den_fwd expected_fwd[..., 1] *= num_fwd / den_fwd # apply Gaussian smoothing expected_fwd[..., 0] = ndimage.filters.gaussian_filter(expected_fwd[..., 0], 3.0) expected_fwd[..., 1] = ndimage.filters.gaussian_filter(expected_fwd[..., 1], 3.0) num_bwd = sigma_x_sq * (F - G) den_bwd = sigma_x_sq * sq_norm_grad_G + sigma_i_sq # This is $J^{P}$ in eq. 4 [Vercauteren09] expected_bwd = -1 * np.array(grad_G) expected_bwd[..., 0] *= num_bwd / den_bwd expected_bwd[..., 1] *= num_bwd / den_bwd # apply Gaussian smoothing expected_bwd[..., 0] = ndimage.filters.gaussian_filter(expected_bwd[..., 0], 3.0) expected_bwd[..., 1] = ndimage.filters.gaussian_filter(expected_bwd[..., 1], 3.0) assert_array_almost_equal(actual_forward, expected_fwd) assert_array_almost_equal(actual_backward, expected_bwd)