def v_cycle_3d(n, k, delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement, depth=0): r"""Multi-resolution Gauss-Seidel solver using V-type cycles Multi-resolution Gauss-Seidel solver: solves the linear system by first filtering (GS-iterate) the current level, then solves for the residual at a coarser resolution and finally refines the solution at the current resolution. This scheme corresponds to the V-cycle proposed by Bruhn and Weickert[1]. [1] Andres Bruhn and Joachim Weickert, "Towards ultimate motion estimation: combining highest accuracy with real-time performance", 10th IEEE International Conference on Computer Vision, 2005. ICCV 2005. Parameters ---------- n : int number of levels of the multi-resolution algorithm (it will be called recursively until level n == 0) k : int the number of iterations at each multi-resolution level delta_field : array, shape (S, R, C) the difference between the static and moving image (the 'derivative w.r.t. time' in the optical flow model) sigma_sq_field : array, shape (S, R, C) the variance of the gray level value at each voxel, according to the EM model (for SSD, it is 1 for all voxels). Inf and 0 values are processed specially to support infinite and zero variance. gradient_field : array, shape (S, R, C, 3) the gradient of the moving image target : array, shape (S, R, C, 3) right-hand side of the linear system to be solved in the Weickert's multi-resolution algorithm lambda_param : float smoothness parameter, the larger its value the smoother the displacement field displacement : array, shape (S, R, C, 3) the displacement field to start the optimization from Returns ------- energy : the energy of the EM (or SSD if sigmafield[...]==1) metric at this iteration """ # pre-smoothing for i in range(k): ssd.iterate_residual_displacement_field_ssd_3d(delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement) if n == 0: energy = ssd.compute_energy_ssd_3d(delta_field) return energy # solve at coarser grid residual = ssd.compute_residual_displacement_field_ssd_3d(delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement, None) sub_residual = np.array(vfu.downsample_displacement_field_3d(residual)) del residual subsigma_sq_field = None if sigma_sq_field is not None: subsigma_sq_field = vfu.downsample_scalar_field_3d(sigma_sq_field) subdelta_field = vfu.downsample_scalar_field_3d(delta_field) subgradient_field = np.array( vfu.downsample_displacement_field_3d(gradient_field)) shape = np.array(displacement.shape).astype(np.int32) sub_displacement = np.zeros( shape=((shape[0]+1)//2, (shape[1]+1)//2, (shape[2]+1)//2, 3), dtype=floating) sublambda_param = lambda_param*0.25 v_cycle_3d(n-1, k, subdelta_field, subsigma_sq_field, subgradient_field, sub_residual, sublambda_param, sub_displacement, depth+1) del subdelta_field del subsigma_sq_field del subgradient_field del sub_residual displacement += vfu.resample_displacement_field_3d(sub_displacement, 0.5 * np.ones(3), shape) del sub_displacement # post-smoothing for i in range(k): ssd.iterate_residual_displacement_field_ssd_3d(delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement) energy = ssd.compute_energy_ssd_3d(delta_field) return energy
def v_cycle_3d(n, k, delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement, depth=0): r"""Multi-resolution Gauss-Seidel solver using V-type cycles Multi-resolution Gauss-Seidel solver: solves the linear system by first filtering (GS-iterate) the current level, then solves for the residual at a coarser resolution and finally refines the solution at the current resolution. This scheme corresponds to the V-cycle proposed by Bruhn and Weickert[1]. [1] Andres Bruhn and Joachim Weickert, "Towards ultimate motion estimation: combining highest accuracy with real-time performance", 10th IEEE International Conference on Computer Vision, 2005. ICCV 2005. Parameters ---------- n : int number of levels of the multi-resolution algorithm (it will be called recursively until level n == 0) k : int the number of iterations at each multi-resolution level delta_field : array, shape (S, R, C) the difference between the static and moving image (the 'derivative w.r.t. time' in the optical flow model) sigma_sq_field : array, shape (S, R, C) the variance of the gray level value at each voxel, according to the EM model (for SSD, it is 1 for all voxels). Inf and 0 values are processed specially to support infinite and zero variance. gradient_field : array, shape (S, R, C, 3) the gradient of the moving image target : array, shape (S, R, C, 3) right-hand side of the linear system to be solved in the Weickert's multi-resolution algorithm lambda_param : float smoothness parameter, the larger its value the smoother the displacement field displacement : array, shape (S, R, C, 3) the displacement field to start the optimization from Returns ------- energy : the energy of the EM (or SSD if sigmafield[...]==1) metric at this iteration """ # pre-smoothing for i in range(k): ssd.iterate_residual_displacement_field_ssd_3d(delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement) if n == 0: energy = ssd.compute_energy_ssd_3d(delta_field) return energy # solve at coarser grid residual = ssd.compute_residual_displacement_field_ssd_3d( delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement, None) sub_residual = np.array(vfu.downsample_displacement_field_3d(residual)) del residual subsigma_sq_field = None if sigma_sq_field is not None: subsigma_sq_field = vfu.downsample_scalar_field_3d(sigma_sq_field) subdelta_field = vfu.downsample_scalar_field_3d(delta_field) subgradient_field = np.array( vfu.downsample_displacement_field_3d(gradient_field)) shape = np.array(displacement.shape).astype(np.int32) sub_displacement = np.zeros(shape=((shape[0] + 1) // 2, (shape[1] + 1) // 2, (shape[2] + 1) // 2, 3), dtype=floating) sublambda_param = lambda_param * 0.25 v_cycle_3d(n - 1, k, subdelta_field, subsigma_sq_field, subgradient_field, sub_residual, sublambda_param, sub_displacement, depth + 1) del subdelta_field del subsigma_sq_field del subgradient_field del sub_residual displacement += vfu.resample_displacement_field_3d(sub_displacement, 0.5 * np.ones(3), shape) del sub_displacement # post-smoothing for i in range(k): ssd.iterate_residual_displacement_field_ssd_3d(delta_field, sigma_sq_field, gradient_field, target, lambda_param, displacement) energy = ssd.compute_energy_ssd_3d(delta_field) return energy
def test_compute_residual_displacement_field_ssd_3d(): #Select arbitrary images' shape (same shape for both images) sh = (20, 15, 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_2 = np.asarray(range(sh[2])) X = np.ndarray(sh + (3,), dtype = np.float64) O = np.ones(sh) X[...,0]= x_0[:, None, None] * O X[...,1]= x_1[None, :, None] * O X[...,2]= x_2[None, None, :] * O #Compute the gradient fields of F and G np.random.seed(9223102) grad_F = X - c_f grad_G = X - c_g Fnoise = np.random.ranf(np.size(grad_F)).reshape(grad_F.shape) * grad_F.max() * 0.1 Fnoise = Fnoise.astype(floating) grad_F += Fnoise Gnoise = np.random.ranf(np.size(grad_G)).reshape(grad_G.shape) * grad_G.max() * 0.1 Gnoise = Gnoise.astype(floating) grad_G += Gnoise #The squared norm of grad_G 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 Fnoise = np.random.ranf(np.size(F)).reshape(F.shape) * F.max() * 0.1 Fnoise = Fnoise.astype(floating) F += Fnoise Gnoise = np.random.ranf(np.size(G)).reshape(G.shape) * G.max() * 0.1 Gnoise = Gnoise.astype(floating) G += Gnoise delta_field = np.array(F - G, dtype = floating) sigma_field = np.random.randn(delta_field.size).reshape(delta_field.shape) sigma_field = sigma_field.astype(floating) #Select some pixels to force sigma_field = infinite inf_sigma = np.random.randint(0, 2, sh[0]*sh[1]*sh[2]) inf_sigma = inf_sigma.reshape(sh) sigma_field[inf_sigma == 1] = np.inf #Select an initial displacement field d = np.random.randn(grad_G.size).reshape(grad_G.shape).astype(floating) #d = np.zeros_like(grad_G, dtype=floating) lambda_param = 1.5 #Implementation under test iut = ssd.compute_residual_displacement_field_ssd_3d #In the first iteration we test the case target=None #In the second iteration, target is not None target = None rtol = 1e-9 atol = 1e-4 for it in range(2): # Sum of differences with the neighbors s = np.zeros_like(d, dtype = np.float64) s[:,:,:-1] += d[:,:,:-1] - d[:,:,1:]#right s[:,:,1:] += d[:,:,1:] - d[:,:,:-1]#left s[:,:-1,:] += d[:,:-1,:] - d[:,1:,:]#down s[:,1:,:] += d[:,1:,:] - d[:,:-1,:]#up s[:-1,:,:] += d[:-1,:,:] - d[1:,:,:]#below s[1:,:,:] += d[1:,:,:] - d[:-1,:,:]#above s *= lambda_param # Dot product of displacement and gradient dp = d[...,0]*grad_G[...,0] + \ d[...,1]*grad_G[...,1] + \ d[...,2]*grad_G[...,2] # Compute expected residual expected = None if target is None: expected = np.zeros_like(grad_G) for i in range(3): expected[...,i] = delta_field*grad_G[...,i] else: expected = target.copy().astype(np.float64) # Expected residuals when sigma != infinte for i in range(3): expected[inf_sigma==0,i] -= grad_G[inf_sigma==0, i] * dp[inf_sigma==0] + \ sigma_field[inf_sigma==0] * s[inf_sigma==0, i] # Expected residuals when sigma == infinte expected[inf_sigma==1] = -1.0 * s[inf_sigma==1] # Test residual field computation starting with residual = None actual = iut(delta_field, sigma_field, grad_G.astype(floating), target, lambda_param, d, None) assert_allclose(actual, expected, rtol = rtol, atol = atol) actual = np.ndarray(actual.shape, dtype=floating) #destroy previous result # Test residual field computation starting with residual is not None iut(delta_field, sigma_field, grad_G.astype(floating), target, lambda_param, d, actual) assert_allclose(actual, expected, rtol = rtol, atol = atol) # Set target for next iteration target = actual # Test Gauss-Seidel step with residual=None and residual=target for residual in [None, target]: expected = d.copy() iterate_residual_field_ssd_3d(delta_field, sigma_field, grad_G.astype(floating), residual, lambda_param, expected) actual = d.copy() ssd.iterate_residual_displacement_field_ssd_3d(delta_field, sigma_field, grad_G.astype(floating), residual, lambda_param, actual) # the numpy linear solver may differ from our custom implementation # we need to increase the tolerance a bit assert_allclose(actual, expected, rtol = rtol, atol = atol*5)