def test_functional_composition(space): """Test composition from the right with an operator.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 func = odl.solvers.L2NormSquared(space) # Verify that an error is raised if an invalid operator is used # (e.g. wrong range) scalar = 2.1 wrong_space = odl.uniform_discr(1, 2, 10) op_wrong = odl.operator.ScalingOperator(wrong_space, scalar) with pytest.raises(OpTypeError): func * op_wrong # Test composition with operator from the right op = odl.operator.ScalingOperator(space, scalar) func_op_comp = func * op assert isinstance(func_op_comp, odl.solvers.Functional) x = noise_element(space) assert almost_equal(func_op_comp(x), func(op(x)), places=places) # Test gradient and derivative with composition from the right assert all_almost_equal(func_op_comp.gradient(x), (op.adjoint * func.gradient * op)(x), places=places) p = noise_element(space) assert all_almost_equal(func_op_comp.derivative(x)(p), (op.adjoint * func.gradient * op)(x).inner(p), places=places)
def test_arithmetic(): """Test that all standard arithmetic works.""" space = odl.rn(3) # Create elements needed for later functional = odl.solvers.L2Norm(space).translated([1, 2, 3]) functional2 = odl.solvers.L2NormSquared(space) operator = odl.IdentityOperator(space) - space.element([4, 5, 6]) x = noise_element(functional.domain) y = noise_element(functional.domain) scalar = np.pi # Simple tests here, more in depth comes later assert functional(x) == functional(x) assert functional(x) != functional2(x) assert (scalar * functional)(x) == scalar * functional(x) assert (scalar * (scalar * functional))(x) == scalar**2 * functional(x) assert (functional * scalar)(x) == functional(scalar * x) assert ((functional * scalar) * scalar)(x) == functional(scalar**2 * x) assert (functional + functional2)(x) == functional(x) + functional2(x) assert (functional - functional2)(x) == functional(x) - functional2(x) assert (functional * operator)(x) == functional(operator(x)) assert all_almost_equal((y * functional)(x), y * functional(x)) assert all_almost_equal((y * (y * functional))(x), (y * y) * functional(x)) assert all_almost_equal((functional * y)(x), functional(y * x)) assert all_almost_equal(((functional * y) * y)(x), functional((y * y) * x))
def test_proximal_convconj_kl_cross_entropy(): """Test for proximal of convex conjugate of cross entropy KL divergence.""" # Image space space = odl.uniform_discr(0, 1, 10) # Data g = space.element(np.arange(10, 0, -1)) # Factory function returning the proximal operator lam = 2 prox_factory = proximal_cconj_kl_cross_entropy(space, lam=lam, g=g) # Initialize the proximal operator of F^* sigma = 0.25 prox = prox_factory(sigma) assert isinstance(prox, odl.Operator) # Element in image space where the proximal operator is evaluated x = space.element(np.arange(-5, 5)) prox_val = prox(x) # Explicit computation: x_verify = x - lam * scipy.special.lambertw( sigma / lam * g * np.exp(x / lam)) assert all_almost_equal(prox_val, x_verify, HIGH_ACC) # Test in-place evaluation x_inplace = space.element() prox(x, out=x_inplace) assert all_almost_equal(x_inplace, x_verify, HIGH_ACC)
def test_ufuncs(): # Cannot use fixture due to bug in pytest H = odl.ProductSpace(odl.Rn(1), odl.Rn(2)) # one arg x = H.element([[-1], [-2, -3]]) z = x.ufunc.absolute() assert all_almost_equal(z, [[1], [2, 3]]) # one arg with out x = H.element([[-1], [-2, -3]]) y = H.element() z = x.ufunc.absolute(out=y) assert y is z assert all_almost_equal(z, [[1], [2, 3]]) # Two args x = H.element([[1], [2, 3]]) y = H.element([[4], [5, 6]]) w = H.element() z = x.ufunc.add(y) assert all_almost_equal(z, [[5], [7, 9]]) # Two args with out x = H.element([[1], [2, 3]]) y = H.element([[4], [5, 6]]) w = H.element() z = x.ufunc.add(y, out=w) assert w is z assert all_almost_equal(z, [[5], [7, 9]])
def test_proximal_quadratic_perturbation(nonneg_scalar, sigma): """Test for the proximal of quadratic perturbation.""" sigma = float(sigma) space = odl.uniform_discr(0, 1, 10) lam = 1.2 prox_factory = proximal_l2_squared(space, lam=lam) # parameter for the quadratic perturbation, needs to be non-negative a = nonneg_scalar # Test without linear term if a != 0: with pytest.raises(ValueError): # negative values not allowed proximal_quadratic_perturbation(prox_factory, -a) prox = proximal_quadratic_perturbation(prox_factory, a)(sigma) x = noise_element(space) expected_result = x / (2 * sigma * (lam + a) + 1) assert all_almost_equal(prox(x), expected_result, places=PLACES) # Test with linear term u = noise_element(space) prox = proximal_quadratic_perturbation(prox_factory, a, u)(sigma) expected_result = (x - sigma * u) / (2 * sigma * (lam + a) + 1) assert all_almost_equal(prox(x), expected_result, places=PLACES)
def test_proximal_l2_wo_data(): """Proximal factory for the L2-norm.""" # Image space space = odl.uniform_discr(0, 1, 10) # Factory function returning the proximal operator lam = 2.0 prox_factory = proximal_l2(space, lam=lam) # Initialize the proximal operator sigma = 3.0 prox = prox_factory(sigma) assert isinstance(prox, odl.Operator) # Elements x = space.element(np.arange(-5, 5)) x_small = x * 0.5 * lam * sigma / x.norm() x_big = x * 2.0 * lam * sigma / x.norm() # Explicit computation: x_small_opt = x_small * 0 x_big_opt = (1 - lam * sigma / x_big.norm()) * x_big assert all_almost_equal(prox(x_small), x_small_opt, PLACES) assert all_almost_equal(prox(x_big), x_big_opt, PLACES)
def test_linear_addition(): A = np.random.rand(4, 3) B = np.random.rand(4, 3) x = np.random.rand(3) y = np.random.rand(4) Aop = MatVecOperator(A) Bop = MatVecOperator(B) xvec = Aop.domain.element(x) yvec = Aop.range.element(y) # Explicit instantiation C = OperatorSum(Aop, Bop) assert C.is_linear assert C.adjoint.is_linear assert all_almost_equal(C(xvec), np.dot(A, x) + np.dot(B, x)) assert all_almost_equal(C.adjoint(yvec), np.dot(A.T, y) + np.dot(B.T, y)) # Using operator overloading assert all_almost_equal((Aop + Bop)(xvec), np.dot(A, x) + np.dot(B, x)) assert all_almost_equal((Aop + Bop).adjoint(yvec), np.dot(A.T, y) + np.dot(B.T, y))
def test_pointwise_inner_weighted(): fspace = odl.uniform_discr([0, 0], [1, 1], (2, 2)) vfspace = ProductSpace(fspace, 3) array = np.array([[[-1, -3], [2, 0]], [[0, 0], [0, 1]], [[-1, 1], [1, 1]]]) weight = np.array([1.0, 2.0, 3.0]) pwinner = PointwiseInner(vfspace, vecfield=array, weighting=weight) testarr = np.array([[[1, 2], [3, 4]], [[0, -1], [0, 1]], [[1, 1], [1, 1]]]) true_inner = np.sum(weight[:, None, None] * testarr * array, axis=0) func = vfspace.element(testarr) func_pwinner = pwinner(func) assert all_almost_equal(func_pwinner, true_inner.reshape(-1)) out = fspace.element() pwinner(func, out=out) assert all_almost_equal(out, true_inner.reshape(-1))
def test_nonlinear_addition(): # Test operator addition A = np.random.rand(4, 3) B = np.random.rand(4, 3) x = np.random.rand(3) Aop = MultiplyAndSquareOp(A) Bop = MultiplyAndSquareOp(B) xvec = Aop.domain.element(x) # Explicit instantiation C = OperatorSum(Aop, Bop) assert not C.is_linear assert all_almost_equal(C(xvec), mult_sq_np(A, x) + mult_sq_np(B, x)) # Using operator overloading assert all_almost_equal((Aop + Bop)(xvec), mult_sq_np(A, x) + mult_sq_np(B, x)) # Verify that unmatched operators domains fail C = np.random.rand(4, 4) Cop = MultiplyAndSquareOp(C) with pytest.raises(TypeError): C = OperatorSum(Aop, Cop)
def test_functional_scale(): r3 = odl.Rn(3) Aop = SumFunctional(r3) x = r3.element([1, 2, 3]) y = 1 # Test a range of scalars (scalar multiplication could implement # optimizations for (-1, 0, 1). scalars = [-1.432, -1, 0, 1, 3.14] for scale in scalars: C = OperatorRightScalarMult(Aop, scale) assert C.is_linear assert C.adjoint.is_linear assert C(x) == scale * np.sum(x) assert all_almost_equal(C.adjoint(y), scale * y * np.ones(3)) assert all_almost_equal(C.adjoint.adjoint(x), C(x)) # Using operator overloading assert (scale * Aop)(x) == scale * np.sum(x) assert (Aop * scale)(x) == scale * np.sum(x) assert all_almost_equal((scale * Aop).adjoint(y), scale * y * np.ones(3)) assert all_almost_equal((Aop * scale).adjoint(y), scale * y * np.ones(3))
def test_wavelet_decomposition3d_and_reconstruction3d(): # Test 3D wavelet decomposition and reconstruction and verify that # they perform as expected x = np.random.rand(16, 16, 16) mode = 'sym' wbasis = pywt.Wavelet('db5') nscales = 1 wavelet_coeffs = wavelet_decomposition3d(x, wbasis, mode, nscales) aaa = wavelet_coeffs[0] reference = pywt.dwtn(x, wbasis, mode) aaa_reference = reference['aaa'] assert all_almost_equal(aaa, aaa_reference) reconstruction = wavelet_reconstruction3d(wavelet_coeffs, wbasis, mode, nscales) reconstruction_reference = pywt.idwtn(reference, wbasis, mode) assert all_almost_equal(reconstruction, reconstruction_reference) assert all_almost_equal(reconstruction, x) assert all_almost_equal(reconstruction_reference, x) wbasis = pywt.Wavelet('db1') nscales = 3 wavelet_coeffs = wavelet_decomposition3d(x, wbasis, mode, nscales) shape_true = (nscales + 1, ) assert all_equal(np.shape(wavelet_coeffs), shape_true) reconstruction = wavelet_reconstruction3d(wavelet_coeffs, wbasis, mode, nscales) assert all_almost_equal(reconstruction, x)
def test_collocation_interpolation_identity(): # Check if interpolation followed by collocation on the same grid # is the identity rect = odl.Rectangle([0, 0], [1, 1]) grid = odl.uniform_sampling(rect, [4, 2], as_midp=True) space = odl.FunctionSpace(rect) dspace = odl.Rn(grid.ntotal) # Testing 'C' and 'F' ordering and all interpolation schemes coll_op_c = GridCollocation(space, grid, dspace, order='C') coll_op_f = GridCollocation(space, grid, dspace, order='F') interp_ops_c = [ NearestInterpolation(space, grid, dspace, order='C', variant='left'), NearestInterpolation(space, grid, dspace, order='C', variant='right'), LinearInterpolation(space, grid, dspace, order='C'), PerAxisInterpolation(space, grid, dspace, order='C', schemes=['linear', 'nearest'])] interp_ops_f = [ NearestInterpolation(space, grid, dspace, order='F', variant='left'), NearestInterpolation(space, grid, dspace, order='F', variant='right'), LinearInterpolation(space, grid, dspace, order='F'), PerAxisInterpolation(space, grid, dspace, order='F', schemes=['linear', 'nearest'])] values = np.arange(1, 9, dtype='float64') for interp_op_c in interp_ops_c: ident_values = coll_op_c(interp_op_c(values)) assert all_almost_equal(ident_values, values) for interp_op_f in interp_ops_f: ident_values = coll_op_f(interp_op_f(values)) assert all_almost_equal(ident_values, values)
def test_multiply(): # Validates multiply against the result on host with randomized data rn = odl.CudaRn(100) x_host, y_host, z_host, x_device, y_device, z_device = _vectors(rn, 3) # Host side calculation z_host[:] = x_host * y_host # Device side calculation rn.multiply(x_device, y_device, out=z_device) assert all_almost_equal([x_device, y_device, z_device], [x_host, y_host, z_host]) # Aliased z_host[:] = z_host * x_host rn.multiply(z_device, x_device, out=z_device) assert all_almost_equal([x_device, z_device], [x_host, z_host]) # Aliased z_host[:] = z_host * z_host rn.multiply(z_device, z_device, out=z_device) assert all_almost_equal(z_device, z_host)
def _test_lincomb(fn, a, b): # Validate lincomb against the result on host with randomized # data and given a,b, contiguous and non-contiguous # Unaliased arguments xarr, yarr, zarr, x, y, z = _vectors(fn, 3) zarr[:] = a * xarr + b * yarr fn.lincomb(a, x, b, y, out=z) assert all_almost_equal([x, y, z], [xarr, yarr, zarr]) # First argument aliased with output xarr, yarr, zarr, x, y, z = _vectors(fn, 3) zarr[:] = a * zarr + b * yarr fn.lincomb(a, z, b, y, out=z) assert all_almost_equal([x, y, z], [xarr, yarr, zarr]) # Second argument aliased with output xarr, yarr, zarr, x, y, z = _vectors(fn, 3) zarr[:] = a * xarr + b * zarr fn.lincomb(a, x, b, z, out=z) assert all_almost_equal([x, y, z], [xarr, yarr, zarr]) # Both arguments aliased with each other xarr, yarr, zarr, x, y, z = _vectors(fn, 3) zarr[:] = a * xarr + b * xarr fn.lincomb(a, x, b, x, out=z) assert all_almost_equal([x, y, z], [xarr, yarr, zarr]) # All aliased xarr, yarr, zarr, x, y, z = _vectors(fn, 3) zarr[:] = a * zarr + b * zarr fn.lincomb(a, z, b, z, out=z) assert all_almost_equal([x, y, z], [xarr, yarr, zarr])
def test_multiply(fn): # Validates multiply against the result on host with randomized data [xarr, yarr, zarr], [x_device, y_device, z_device] = example_vectors(fn, 3) # Host side calculation zarr[:] = xarr * yarr # Device side calculation fn.multiply(x_device, y_device, out=z_device) assert all_almost_equal([x_device, y_device, z_device], [xarr, yarr, zarr]) # Aliased zarr[:] = xarr * zarr fn.multiply(z_device, x_device, out=z_device) assert all_almost_equal([x_device, z_device], [xarr, zarr]) # Aliased zarr[:] = zarr * zarr fn.multiply(z_device, z_device, out=z_device) assert all_almost_equal(z_device, zarr)
def test_bwt2d(): # 2D test n = 16 x = np.zeros((n, n)) x[5:10, 5:10] = 1 wbasis = 'josbiorth5' nscales = 3 # Define a discretized domain domain = odl.FunctionSpace(odl.Rectangle([-1, -1], [1, 1])) nPoints = np.array([n, n]) disc_domain = odl.uniform_discr_fromspace(domain, nPoints) disc_phantom = disc_domain.element(x) # Create the discrete wavelet transform operator. # Only the domain of the operator needs to be defined Bop = BiorthWaveletTransform(disc_domain, nscales, wbasis) Bop2 = InverseAdjBiorthWaveletTransform(disc_domain, nscales, wbasis) # Compute the discrete wavelet transform of discrete imput image coeffs = Bop(disc_phantom) coeffs2 = Bop2(disc_phantom) reconstruction = Bop.inverse(coeffs) reconstruction2 = Bop2.inverse(coeffs2) assert all_almost_equal(reconstruction.asarray(), x) assert all_almost_equal(reconstruction2.asarray(), x)
def test_wavelet_transform(wave_impl, shape_setup, floating_dtype): # Verify that the operator works as expected wavelet, pad_mode, nlevels, shape, _ = shape_setup ndim = len(shape) space = odl.uniform_discr([-1] * ndim, [1] * ndim, shape, dtype=floating_dtype) image = noise_element(space) # TODO: check more error scenarios if wave_impl == 'pywt' and pad_mode == 'constant': with pytest.raises(ValueError): wave_trafo = odl.trafos.WaveletTransform( space, wavelet, nlevels, pad_mode, pad_const=1, impl=wave_impl) wave_trafo = odl.trafos.WaveletTransform( space, wavelet, nlevels, pad_mode, impl=wave_impl) assert wave_trafo.domain.dtype == floating_dtype assert wave_trafo.range.dtype == floating_dtype wave_trafo_inv = wave_trafo.inverse assert wave_trafo_inv.domain.dtype == floating_dtype assert wave_trafo_inv.range.dtype == floating_dtype assert wave_trafo_inv.nlevels == wave_trafo.nlevels assert wave_trafo_inv.wavelet == wave_trafo.wavelet assert wave_trafo_inv.pad_mode == wave_trafo.pad_mode assert wave_trafo_inv.pad_const == wave_trafo.pad_const assert wave_trafo_inv.pywt_pad_mode == wave_trafo.pywt_pad_mode coeffs = wave_trafo(image) reco_image = wave_trafo.inverse(coeffs) assert all_almost_equal(image.real, reco_image.real) assert all_almost_equal(image, reco_image)
def test_pointwise_inner_complex(): fspace = odl.uniform_discr([0, 0], [1, 1], (2, 2), dtype=complex) vfspace = ProductSpace(fspace, 3) array = np.array([[[-1 - 1j, -3], [2, 2j]], [[-1j, 0], [0, 1]], [[-1, 1 + 2j], [1, 1]]]) pwinner = PointwiseInner(vfspace, vecfield=array) testarr = np.array([[[1 + 1j, 2], [3, 4 - 2j]], [[0, -1], [0, 1]], [[1j, 1j], [1j, 1j]]]) true_inner = np.sum(testarr * array.conj(), axis=0) func = vfspace.element(testarr) func_pwinner = pwinner(func) assert all_almost_equal(func_pwinner, true_inner.reshape(-1)) out = fspace.element() pwinner(func, out=out) assert all_almost_equal(out, true_inner.reshape(-1))
def test_collocation_interpolation_identity(): # Check if interpolation followed by collocation on the same grid # is the identity rect = odl.IntervalProd([0, 0], [1, 1]) part = odl.uniform_partition_fromintv(rect, [4, 2]) space = odl.FunctionSpace(rect) dspace = odl.rn(part.size) coll_op_c = PointCollocation(space, part, dspace, order='C') coll_op_f = PointCollocation(space, part, dspace, order='F') interp_ops_c = [ NearestInterpolation(space, part, dspace, variant='left', order='C'), NearestInterpolation(space, part, dspace, variant='right', order='C'), LinearInterpolation(space, part, dspace, order='C'), PerAxisInterpolation(space, part, dspace, order='C', schemes=['linear', 'nearest'])] interp_ops_f = [ NearestInterpolation(space, part, dspace, variant='left', order='F'), NearestInterpolation(space, part, dspace, variant='right', order='F'), LinearInterpolation(space, part, dspace, order='F'), PerAxisInterpolation(space, part, dspace, order='F', schemes=['linear', 'nearest'])] values = np.arange(1, 9, dtype='float64') for interp_op_c in interp_ops_c: ident_values = coll_op_c(interp_op_c(values)) assert all_almost_equal(ident_values, values) for interp_op_f in interp_ops_f: ident_values = coll_op_f(interp_op_f(values)) assert all_almost_equal(ident_values, values)
def test_pointwise_norm_weighted(exponent): fspace = odl.uniform_discr([0, 0], [1, 1], (2, 2)) vfspace = ProductSpace(fspace, 3) weight = np.array([1.0, 2.0, 3.0]) pwnorm = PointwiseNorm(vfspace, exponent, weighting=weight) testarr = np.array([[[1, 2], [3, 4]], [[0, -1], [0, 1]], [[1, 1], [1, 1]]]) if exponent in (1.0, float('inf')): true_norm = np.linalg.norm(weight[:, None, None] * testarr, ord=exponent, axis=0) else: true_norm = np.linalg.norm( weight[:, None, None] ** (1 / exponent) * testarr, ord=exponent, axis=0) func = vfspace.element(testarr) func_pwnorm = pwnorm(func) assert all_almost_equal(func_pwnorm, true_norm.reshape(-1)) out = fspace.element() pwnorm(func, out=out) assert all_almost_equal(out, true_norm.reshape(-1))
def test_power(fn_impl, power): space = odl.uniform_discr([0, 0], [1, 1], [2, 2], impl=fn_impl) x_arr, x = noise_elements(space, 1) x_pos_arr = np.abs(x_arr) x_neg_arr = -x_pos_arr x_pos = np.abs(x) x_neg = -x_pos if int(power) != power: # Make input positive to get real result for y in [x_pos_arr, x_neg_arr, x_pos, x_neg]: y += 0.1 true_pos_pow = np.power(x_pos_arr, power) true_neg_pow = np.power(x_neg_arr, power) if int(power) != power and fn_impl == 'cuda': with pytest.raises(ValueError): x_pos ** power with pytest.raises(ValueError): x_pos **= power else: assert all_almost_equal(x_pos ** power, true_pos_pow) assert all_almost_equal(x_neg ** power, true_neg_pow) x_pos **= power x_neg **= power assert all_almost_equal(x_pos, true_pos_pow) assert all_almost_equal(x_neg, true_neg_pow)
def test_operators(arithmetic_op): # Test of the operators `+`, `-`, etc work as expected by numpy space = odl.rn(3) pspace = odl.ProductSpace(space, 2) # Interactions with scalars for scalar in [-31.2, -1, 0, 1, 2.13]: # Left op x_arr, x = noise_elements(pspace) if scalar == 0 and arithmetic_op in [operator.truediv, operator.itruediv]: # Check for correct zero division behaviour with pytest.raises(ZeroDivisionError): y = arithmetic_op(x, scalar) else: y_arr = arithmetic_op(x_arr, scalar) y = arithmetic_op(x, scalar) assert all_almost_equal([x, y], [x_arr, y_arr]) # Right op x_arr, x = noise_elements(pspace) y_arr = arithmetic_op(scalar, x_arr) y = arithmetic_op(scalar, x) assert all_almost_equal([x, y], [x_arr, y_arr]) # Verify that the statement z=op(x, y) gives equivalent results to NumPy x_arr, x = noise_elements(space, 1) y_arr, y = noise_elements(pspace, 1) # non-aliased left if arithmetic_op in [operator.iadd, operator.isub, operator.itruediv, operator.imul]: # Check for correct error since in-place op is not possible here with pytest.raises(TypeError): z = arithmetic_op(x, y) else: z_arr = arithmetic_op(x_arr, y_arr) z = arithmetic_op(x, y) assert all_almost_equal([x, y, z], [x_arr, y_arr, z_arr]) # non-aliased right z_arr = arithmetic_op(y_arr, x_arr) z = arithmetic_op(y, x) assert all_almost_equal([x, y, z], [x_arr, y_arr, z_arr]) # aliased operation z_arr = arithmetic_op(y_arr, y_arr) z = arithmetic_op(y, y) assert all_almost_equal([x, y, z], [x_arr, y_arr, z_arr])
def test_proximal_l2_with_data(): """Proximal factory for the L2-norm with data term.""" # Image space space = odl.uniform_discr(0, 1, 10) # Create data g = space.element(np.arange(-5, 5)) # Factory function returning the proximal operator lam = 2.0 prox_factory = proximal_l2(space, lam=lam, g=g) # Initialize the proximal operator sigma = 3.0 prox = prox_factory(sigma) assert isinstance(prox, odl.Operator) # Elements x = space.element(np.arange(-5, 5)) x_small = g + x * 0.5 * lam * sigma / x.norm() x_big = g + x * 2.0 * lam * sigma / x.norm() # Explicit computation: x_small_opt = g const = lam * sigma / (x_big - g).norm() x_big_opt = (1 - const) * x_big + const * g assert all_almost_equal(prox(x_small), x_small_opt, HIGH_ACC) assert all_almost_equal(prox(x_big), x_big_opt, HIGH_ACC)
def test_gradient(method, impl, padding): """Discretized spatial gradient operator.""" with pytest.raises(TypeError): Gradient(odl.Rn(1), method=method) if isinstance(padding, tuple): padding_method, padding_value = padding else: padding_method, padding_value = padding, None # DiscreteLp Vector discr_space = odl.uniform_discr([0, 0], [1, 1], DATA_2D.shape, impl=impl) dom_vec = discr_space.element(DATA_2D) # computation of gradient components with helper function dx0, dx1 = discr_space.cell_sides diff_0 = finite_diff(DATA_2D, axis=0, dx=dx0, method=method, padding_method=padding_method, padding_value=padding_value) diff_1 = finite_diff(DATA_2D, axis=1, dx=dx1, method=method, padding_method=padding_method, padding_value=padding_value) # gradient grad = Gradient(discr_space, method=method, padding_method=padding_method, padding_value=padding_value) grad_vec = grad(dom_vec) assert len(grad_vec) == DATA_2D.ndim assert all_almost_equal(grad_vec[0].asarray(), diff_0) assert all_almost_equal(grad_vec[1].asarray(), diff_1) # Test adjoint operator derivative = grad.derivative() ran_vec = derivative.range.element([DATA_2D, DATA_2D ** 2]) deriv_grad_vec = derivative(dom_vec) adj_grad_vec = derivative.adjoint(ran_vec) lhs = ran_vec.inner(deriv_grad_vec) rhs = dom_vec.inner(adj_grad_vec) # Check not to use trivial data assert lhs != 0 assert rhs != 0 assert almost_equal(lhs, rhs) # higher dimensional arrays lin_size = 3 for ndim in [1, 3, 6]: # DiscreteLp Vector space = odl.uniform_discr([0.] * ndim, [1.] * ndim, [lin_size] * ndim) dom_vec = odl.phantom.cuboid(space, [0.2] * ndim, [0.8] * ndim) # gradient grad = Gradient(space, method=method, padding_method=padding_method, padding_value=padding_value) grad(dom_vec)
def evaluate(operator, point, expected): """Assert that operator(point) == expected.""" assert all_almost_equal(operator(point), expected) out = operator.range.element() operator(point, out=out) assert all_almost_equal(out, expected)
def test_L2_norm(space, sigma): """Test the L2-norm.""" func = odl.solvers.L2Norm(space) x = noise_element(space) x_norm = x.norm() # Test functional evaluation expected_result = np.sqrt((x ** 2).inner(space.one())) assert pytest.approx(func(x), expected_result) # Test gradient if x_norm > 0: expected_result = x / x.norm() assert all_almost_equal(func.gradient(x), expected_result) # Verify that the gradient at zero is zero assert all_almost_equal(func.gradient(func.domain.zero()), space.zero()) # Test proximal operator - expecting # x * (1 - sigma/||x||) if ||x|| > sigma, 0 else norm_less_than_sigma = 0.99 * sigma * x / x_norm assert all_almost_equal(func.proximal(sigma)(norm_less_than_sigma), space.zero()) norm_larger_than_sigma = 1.01 * sigma * x / x_norm expected_result = (norm_larger_than_sigma * (1.0 - sigma / norm_larger_than_sigma.norm())) assert all_almost_equal(func.proximal(sigma)(norm_larger_than_sigma), expected_result) # Test convex conjugate func_cc = func.convex_conj # Test evaluation of the convex conjugate - expecting # 0 if ||x|| < 1, infty else norm_larger_than_one = 1.01 * x / x_norm assert func_cc(norm_larger_than_one) == np.inf norm_less_than_one = 0.99 * x / x_norm assert func_cc(norm_less_than_one) == 0 # Gradient of the convex conjugate (not implemeted) with pytest.raises(NotImplementedError): func_cc.gradient # Test the proximal of the convex conjugate - expecting # x if ||x||_2 < 1, x/||x|| else if x_norm < 1: expected_result = x else: expected_result = x / x_norm assert all_almost_equal(func_cc.proximal(sigma)(x), expected_result) # Verify that the biconjugate is the functional itself func_cc_cc = func_cc.convex_conj assert pytest.approx(func_cc_cc(x), func(x))
def test_translation_of_functional(space): """Test for the translation of a functional: (f(. - y))^*.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 # The translation; an element in the domain translation = noise_element(space) test_functional = odl.solvers.L2NormSquared(space) translated_functional = test_functional.translated(translation) x = noise_element(space) # Test for evaluation of the functional expected_result = test_functional(x - translation) assert all_almost_equal(translated_functional(x), expected_result, places=places) # Test for the gradient expected_result = test_functional.gradient(x - translation) translated_gradient = translated_functional.gradient assert all_almost_equal(translated_gradient(x), expected_result, places=places) # Test for proximal sigma = 1.2 # The helper function below is tested explicitly in proximal_utils_test expected_result = odl.solvers.proximal_translation( test_functional.proximal, translation)(sigma)(x) assert all_almost_equal(translated_functional.proximal(sigma)(x), expected_result, places=places) # Test for conjugate functional # The helper function below is tested explicitly further down in this file expected_result = odl.solvers.FunctionalLinearPerturb( test_functional.convex_conj, translation)(x) assert all_almost_equal(translated_functional.convex_conj(x), expected_result, places=places) # Test for derivative in direction p p = noise_element(space) # Explicit computation in point x, in direction p: <x/2 + translation, p> expected_result = p.inner(test_functional.gradient(x - translation)) assert all_almost_equal(translated_functional.derivative(x)(p), expected_result, places=places) # Test for optimized implementation, when translating a translated # functional second_translation = noise_element(space) double_translated_functional = translated_functional.translated( second_translation) # Evaluation assert almost_equal(double_translated_functional(x), test_functional(x - translation - second_translation), places=places)
def test_pspace_op_sum_call(): r3 = odl.Rn(3) I = odl.IdentityOperator(r3) op = odl.ProductSpaceOperator([I, I]) x = r3.element([1, 2, 3]) y = r3.element([7, 8, 9]) z = op.domain.element([x, y]) assert all_almost_equal(op(z)[0], x + y) assert all_almost_equal(op(z, out=op.range.element())[0], x + y)
def _impl_test_ufuncs(fn, name, n_args, n_out): # Get the ufunc from numpy as reference ufunc = getattr(np, name) # Create some data data = _vectors(fn, n_args + n_out) in_arrays = data[:n_args] out_arrays = data[n_args:n_args + n_out] data_vector = data[n_args + n_out] in_vectors = data[1 + n_args + n_out:2 * n_args + n_out] out_vectors = data[2 * n_args + n_out:] # Verify type assert isinstance(data_vector.ufunc, odl.util.ufuncs.DiscreteLpVectorUFuncs) # Out of place: np_result = ufunc(*in_arrays) vec_fun = getattr(data_vector.ufunc, name) odl_result = vec_fun(*in_vectors) assert all_almost_equal(np_result, odl_result) # Test type of output if n_out == 1: assert isinstance(odl_result, fn.element_type) elif n_out > 1: for i in range(n_out): assert isinstance(odl_result[i], fn.element_type) # In place: np_result = ufunc(*(in_arrays + out_arrays)) vec_fun = getattr(data_vector.ufunc, name) odl_result = vec_fun(*(in_vectors + out_vectors)) assert all_almost_equal(np_result, odl_result) # Test inplace actually holds: if n_out == 1: assert odl_result is out_vectors[0] elif n_out > 1: for i in range(n_out): assert odl_result[i] is out_vectors[i] # Test out of place with np data np_result = ufunc(*in_arrays) vec_fun = getattr(data_vector.ufunc, name) odl_result = vec_fun(*in_arrays[1:]) assert all_almost_equal(np_result, odl_result) # Test type of output if n_out == 1: assert isinstance(odl_result, fn.element_type) elif n_out > 1: for i in range(n_out): assert isinstance(odl_result[i], fn.element_type)
def test_multiplication_with_vector(space): """Test for multiplying a functional with a vector, both left and right.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 x = noise_element(space) y = noise_element(space) func = odl.solvers.L2NormSquared(space) wrong_space = odl.uniform_discr(1, 2, 10) y_other_space = noise_element(wrong_space) # Multiplication from the right. Make sure it is a # FunctionalRightVectorMult func_times_y = func * y assert isinstance(func_times_y, odl.solvers.FunctionalRightVectorMult) expected_result = func(y * x) assert almost_equal(func_times_y(x), expected_result, places=places) # Test for the gradient. # Explicit calculations: 2*y*y*x expected_result = 2.0 * y * y * x assert all_almost_equal(func_times_y.gradient(x), expected_result, places=places) # Test for convex_conj cc_func_times_y = func_times_y.convex_conj # Explicit calculations: 1/4 * ||x/y||_2^2 expected_result = 1.0 / 4.0 * (x / y).norm()**2 assert almost_equal(cc_func_times_y(x), expected_result, places=places) # Make sure that right muliplication is not allowed with vector from # another space with pytest.raises(TypeError): func * y_other_space # Multiplication from the left. Make sure it is a FunctionalLeftVectorMult y_times_func = y * func assert isinstance(y_times_func, odl.FunctionalLeftVectorMult) expected_result = y * func(x) assert all_almost_equal(y_times_func(x), expected_result, places=places) # Now, multiplication with vector from another space is ok (since it is the # same as scaling that vector with the scalar returned by the functional). y_other_times_func = y_other_space * func assert isinstance(y_other_times_func, odl.FunctionalLeftVectorMult) expected_result = y_other_space * func(x) assert all_almost_equal(y_other_times_func(x), expected_result, places=places)
def test_translation_of_functional(space): """Test for the translation of a functional: (f(. - y))^*.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 # The translation; an element in the domain translation = noise_element(space) test_functional = odl.solvers.L2NormSquared(space) translated_functional = test_functional.translated(translation) x = noise_element(space) # Test for evaluation of the functional expected_result = test_functional(x - translation) assert all_almost_equal(translated_functional(x), expected_result, places=places) # Test for the gradient expected_result = test_functional.gradient(x - translation) translated_gradient = translated_functional.gradient assert all_almost_equal(translated_gradient(x), expected_result, places=places) # Test for proximal sigma = 1.2 # The helper function below is tested explicitly in proximal_utils_test expected_result = odl.solvers.proximal_translation( test_functional.proximal, translation)(sigma)(x) assert all_almost_equal(translated_functional.proximal(sigma)(x), expected_result, places=places) # Test for conjugate functional # The helper function below is tested explicitly further down in this file expected_result = odl.solvers.FunctionalQuadraticPerturb( test_functional.convex_conj, linear_term=translation)(x) assert all_almost_equal(translated_functional.convex_conj(x), expected_result, places=places) # Test for derivative in direction p p = noise_element(space) # Explicit computation in point x, in direction p: <x/2 + translation, p> expected_result = p.inner(test_functional.gradient(x - translation)) assert all_almost_equal(translated_functional.derivative(x)(p), expected_result, places=places) # Test for optimized implementation, when translating a translated # functional second_translation = noise_element(space) double_translated_functional = translated_functional.translated( second_translation) # Evaluation assert almost_equal(double_translated_functional(x), test_functional(x - translation - second_translation), places=places)
def test_zero(fn): assert all_almost_equal(fn.zero(), [0] * fn.size)
def test_ufuncs(odl_tspace_impl, odl_ufunc): """Test ufuncs in ``x.ufuncs`` against direct Numpy ufuncs.""" impl = odl_tspace_impl space = odl.uniform_discr([0, 0], [1, 1], (2, 3), impl=impl) name = odl_ufunc # Get the ufunc from numpy as reference npy_ufunc = getattr(np, name) nin = npy_ufunc.nin nout = npy_ufunc.nout if (np.issubsctype(space.dtype, np.floating) and name in [ 'bitwise_and', 'bitwise_or', 'bitwise_xor', 'invert', 'left_shift', 'right_shift' ]): # Skip integer only methods if floating point type return # Create some data arrays, elements = noise_elements(space, nin + nout) in_arrays = arrays[:nin] out_arrays = arrays[nin:] data_elem = elements[0] out_elems = elements[nin:] if nout == 1: out_arr_kwargs = {'out': out_arrays[0]} out_elem_kwargs = {'out': out_elems[0]} elif nout > 1: out_arr_kwargs = {'out': out_arrays[:nout]} out_elem_kwargs = {'out': out_elems[:nout]} # Get function to call, using both interfaces: # - vec.ufunc(other_args) # - np.ufunc(vec, other_args) elem_fun_old = getattr(data_elem.ufuncs, name) in_elems_old = elements[1:nin] elem_fun_new = npy_ufunc in_elems_new = elements[:nin] # Out-of-place with np.errstate(all='ignore'): # avoid pytest warnings npy_result = npy_ufunc(*in_arrays) odl_result_old = elem_fun_old(*in_elems_old) assert all_almost_equal(npy_result, odl_result_old) odl_result_new = elem_fun_new(*in_elems_new) assert all_almost_equal(npy_result, odl_result_new) # Test type of output if nout == 1: assert isinstance(odl_result_old, space.element_type) assert isinstance(odl_result_new, space.element_type) elif nout > 1: for i in range(nout): assert isinstance(odl_result_old[i], space.element_type) assert isinstance(odl_result_new[i], space.element_type) # In-place with ODL objects as `out` with np.errstate(all='ignore'): # avoid pytest warnings npy_result = npy_ufunc(*in_arrays, **out_arr_kwargs) odl_result_old = elem_fun_old(*in_elems_old, **out_elem_kwargs) assert all_almost_equal(npy_result, odl_result_old) odl_result_new = elem_fun_new(*in_elems_new, **out_elem_kwargs) assert all_almost_equal(npy_result, odl_result_new) # Check that returned stuff refers to given out if nout == 1: assert odl_result_old is out_elems[0] assert odl_result_new is out_elems[0] elif nout > 1: for i in range(nout): assert odl_result_old[i] is out_elems[i] assert odl_result_new[i] is out_elems[i] # In-place with Numpy array as `out` for new interface out_arrays_new = tuple(np.empty_like(arr) for arr in out_arrays) if nout == 1: out_arr_kwargs_new = {'out': out_arrays_new[0]} elif nout > 1: out_arr_kwargs_new = {'out': out_arrays_new[:nout]} with np.errstate(all='ignore'): # avoid pytest warnings odl_result_arr_new = elem_fun_new(*in_elems_new, **out_arr_kwargs_new) assert all_almost_equal(npy_result, odl_result_arr_new) if nout == 1: assert odl_result_arr_new is out_arrays_new[0] elif nout > 1: for i in range(nout): assert odl_result_arr_new[i] is out_arrays_new[i] # In-place with data container (tensor) as `out` for new interface out_tensors_new = tuple( space.tspace.element(np.empty_like(arr)) for arr in out_arrays) if nout == 1: out_tens_kwargs_new = {'out': out_tensors_new[0]} elif nout > 1: out_tens_kwargs_new = {'out': out_tensors_new[:nout]} with np.errstate(all='ignore'): # avoid pytest warnings odl_result_tens_new = elem_fun_new(*in_elems_new, **out_tens_kwargs_new) assert all_almost_equal(npy_result, odl_result_tens_new) if nout == 1: assert odl_result_tens_new is out_tensors_new[0] elif nout > 1: for i in range(nout): assert odl_result_tens_new[i] is out_tensors_new[i] # Check `ufunc.at` indices = ([0, 0, 1], [0, 1, 2]) mod_array = in_arrays[0].copy() mod_elem = in_elems_new[0].copy() if nout > 1: return # currently not supported by Numpy if nin == 1: with np.errstate(all='ignore'): # avoid pytest warnings npy_result = npy_ufunc.at(mod_array, indices) odl_result = npy_ufunc.at(mod_elem, indices) elif nin == 2: other_array = in_arrays[1][indices] other_elem = in_elems_new[1][indices] with np.errstate(all='ignore'): # avoid pytest warnings npy_result = npy_ufunc.at(mod_array, indices, other_array) odl_result = npy_ufunc.at(mod_elem, indices, other_elem) assert all_almost_equal(odl_result, npy_result) # Check `ufunc.reduce` if nin == 2 and nout == 1: in_array = in_arrays[0] in_elem = in_elems_new[0] # We only test along one axis since some binary ufuncs are not # re-orderable, in which case Numpy raises a ValueError with np.errstate(all='ignore'): # avoid pytest warnings npy_result = npy_ufunc.reduce(in_array) odl_result = npy_ufunc.reduce(in_elem) assert all_almost_equal(odl_result, npy_result) # In-place using `out` (with ODL vector and array) out_elem = odl_result.space.element() out_array = np.empty(odl_result.shape, dtype=odl_result.dtype) npy_ufunc.reduce(in_elem, out=out_elem) npy_ufunc.reduce(in_elem, out=out_array) assert all_almost_equal(out_elem, odl_result) assert all_almost_equal(out_array, odl_result) # Using a specific dtype try: npy_result = npy_ufunc.reduce(in_array, dtype=complex) except TypeError: # Numpy finds no matching loop, bail out return else: odl_result = npy_ufunc.reduce(in_elem, dtype=complex) assert odl_result.dtype == npy_result.dtype assert all_almost_equal(odl_result, npy_result)
def test_conebeam_source_detector_shifts(): """Test source/detector shifts in 3d cone beam geometry.""" full_angle = np.pi n_angles = 2 * 7 apart = odl.uniform_partition(0, full_angle, n_angles) dpart = odl.uniform_partition([-1, -1], [1, 1], (10, 10)) src_rad = 10 det_rad = 5 pitch = 3 # Source positions with flying focal spot should correspond to # source positions of 2 geometries with different starting positions shift1 = np.array([2.0, -3.0, 1.0]) shift2 = np.array([-2.0, 3.0, -1.0]) init = np.array([1, 0, 0], dtype=np.float32) ffs = partial(odl.tomo.flying_focal_spot, apart=apart, shifts=[shift1, shift2]) geom_ffs = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad, src_to_det_init=init, src_shift_func=ffs, pitch=pitch) # angles must be shifted to match discretization of apart ang1 = -full_angle / (n_angles * 2) apart1 = odl.uniform_partition(ang1, full_angle + ang1, n_angles // 2) ang2 = full_angle / (n_angles * 2) apart2 = odl.uniform_partition(ang2, full_angle + ang2, n_angles // 2) init1 = init + np.array([0, shift1[1], 0]) / (src_rad + shift1[0]) init2 = init + np.array([0, shift2[1], 0]) / (src_rad + shift2[0]) # radius also changes when a shift is applied src_rad1 = np.linalg.norm(np.array([src_rad + shift1[0], shift1[1], 0])) src_rad2 = np.linalg.norm(np.array([src_rad + shift2[0], shift2[1], 0])) geom1 = odl.tomo.ConeBeamGeometry(apart1, dpart, src_rad1, det_rad, src_to_det_init=init1, offset_along_axis=shift1[2], pitch=pitch) geom2 = odl.tomo.ConeBeamGeometry(apart2, dpart, src_rad2, det_rad, src_to_det_init=init2, offset_along_axis=shift2[2], pitch=pitch) sp1 = geom1.src_position(geom1.angles) sp2 = geom2.src_position(geom2.angles) sp = geom_ffs.src_position(geom_ffs.angles) assert all_almost_equal(sp[0::2], sp1) assert all_almost_equal(sp[1::2], sp2) # detector positions are not affected by flying focal spot geom = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad, src_to_det_init=init, pitch=pitch) assert all_almost_equal(geom.det_refpoint(geom.angles), geom_ffs.det_refpoint(geom_ffs.angles)) # However, detector can be shifted similarly as the source coef = det_rad / src_rad def det_shift(angle): return ffs(angle) * coef geom_ds = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad, src_to_det_init=init, det_shift_func=det_shift, pitch=pitch) det_rad1 = src_rad1 / src_rad * det_rad det_rad2 = src_rad2 / src_rad * det_rad geom1 = odl.tomo.ConeBeamGeometry(apart1, dpart, src_rad, det_rad1, src_to_det_init=init1, offset_along_axis=shift1[2] * coef, pitch=pitch) geom2 = odl.tomo.ConeBeamGeometry(apart2, dpart, src_rad, det_rad2, src_to_det_init=init2, offset_along_axis=shift2[2] * coef, pitch=pitch) dr1 = geom1.det_refpoint(geom1.angles) dr2 = geom2.det_refpoint(geom2.angles) dr = geom_ds.det_refpoint(geom_ds.angles) assert all_almost_equal(dr[0::2], dr1) assert all_almost_equal(dr[1::2], dr2) # source positions are not affected assert all_almost_equal(geom.src_position(geom.angles), geom_ds.src_position(geom_ds.angles))
def test_ndarray_init(fn): x0 = np.arange(fn.size) x = fn.element(x0) assert all_almost_equal(x0, x)
def test_uniform_discr_fromdiscr_one_attr(): # Change 1 attribute discr = odl.uniform_discr([0, -1], [1, 1], [10, 5]) # csides = [0.1, 0.4] # min_pt -> translate, keep cells new_min_pt = [3, 7] true_new_max_pt = [4, 9] new_discr = odl.uniform_discr_fromdiscr(discr, min_pt=new_min_pt) assert all_almost_equal(new_discr.min_pt, new_min_pt) assert all_almost_equal(new_discr.max_pt, true_new_max_pt) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, discr.cell_sides) # max_pt -> translate, keep cells new_max_pt = [3, 7] true_new_min_pt = [2, 5] new_discr = odl.uniform_discr_fromdiscr(discr, max_pt=new_max_pt) assert all_almost_equal(new_discr.min_pt, true_new_min_pt) assert all_almost_equal(new_discr.max_pt, new_max_pt) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, discr.cell_sides) # shape -> resize cells, keep corners new_shape = (5, 20) true_new_csides = [0.2, 0.1] new_discr = odl.uniform_discr_fromdiscr(discr, shape=new_shape) assert all_almost_equal(new_discr.min_pt, discr.min_pt) assert all_almost_equal(new_discr.max_pt, discr.max_pt) assert all_equal(new_discr.shape, new_shape) assert all_almost_equal(new_discr.cell_sides, true_new_csides) # cell_sides -> resize cells, keep corners new_csides = [0.5, 0.2] true_new_shape = (2, 10) new_discr = odl.uniform_discr_fromdiscr(discr, cell_sides=new_csides) assert all_almost_equal(new_discr.min_pt, discr.min_pt) assert all_almost_equal(new_discr.max_pt, discr.max_pt) assert all_equal(new_discr.shape, true_new_shape) assert all_almost_equal(new_discr.cell_sides, new_csides)
def test_parallel_2d_props(shift): """Test basic properties of 2D parallel geometries.""" full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition(0, 1, 10) translation = np.array([shift, shift], dtype=float) geom = odl.tomo.Parallel2dGeometry(apart, dpart, translation=translation) assert geom.ndim == 2 assert isinstance(geom.detector, odl.tomo.Flat1dDetector) # Check defaults assert all_almost_equal(geom.det_pos_init, translation + [0, 1]) assert all_almost_equal(geom.det_refpoint(0), geom.det_pos_init) assert all_almost_equal(geom.det_point_position(0, 0), geom.det_pos_init) assert all_almost_equal(geom.det_axis_init, [1, 0]) assert all_almost_equal(geom.det_axis(0), [1, 0]) assert all_almost_equal(geom.translation, translation) # Check that we first rotate, then shift along the rotated axis, which # is equivalent to shifting first and then rotating. # Here we expect to rotate the reference point to [-1, 0] and then shift # by 1 (=detector param) along the detector axis [0, 1] at that angle. # Global translation should be added afterwards. assert all_almost_equal(geom.det_point_position(np.pi / 2, 1), translation + [-1, 1]) assert all_almost_equal(geom.det_axis(np.pi / 2), [0, 1]) # Detector to source vector, should be independent of the detector # parameter and of translation. # At pi/2 it should point into the (+x) direction. assert all_almost_equal(geom.det_to_src(np.pi / 2, 0), [1, 0]) assert all_almost_equal(geom.det_to_src(np.pi / 2, 1), [1, 0]) # Rotation matrix, should correspond to counter-clockwise rotation assert all_almost_equal(geom.rotation_matrix(np.pi / 2), [[0, -1], [1, 0]]) # Make sure that the boundary cases are treated as valid geom.det_point_position(0, 0) geom.det_point_position(full_angle, 1) # Invalid parameter with pytest.raises(ValueError): geom.rotation_matrix(2 * full_angle) # Check that str and repr work without crashing and return something assert str(geom) assert repr(geom)
def test_list_init(fn): x_list = list(range(fn.size)) x = fn.element(x_list) assert all_almost_equal(x, x_list)
def test_helical_cone_flat(): """Conebeam geometry with helical acquisition and a flat 2D detector.""" # Parameters full_angle = 2 * np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], [10, 10]) src_rad = 10.0 det_rad = 5.0 pitch = 1.5 with pytest.raises(TypeError): odl.tomo.HelicalConeFlatGeometry([0, 1], dpart, src_rad, det_rad, pitch) with pytest.raises(TypeError): odl.tomo.HelicalConeFlatGeometry(apart, [0, 1], src_rad, det_rad, pitch) with pytest.raises(ValueError): odl.tomo.HelicalConeFlatGeometry(apart, dpart, -1, det_rad, pitch) with pytest.raises(ValueError): odl.tomo.HelicalConeFlatGeometry(apart, dpart, src_rad, -1, pitch) # Initialize geom = odl.tomo.HelicalConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch) assert all_almost_equal(geom.angles, apart.points().ravel()) with pytest.raises(ValueError): geom.det_refpoint(2 * full_angle) assert np.linalg.norm(geom.det_refpoint(0)) == det_rad assert almost_equal(np.linalg.norm(geom.det_refpoint(np.pi / 4)[0:2]), det_rad) assert geom.ndim == 3 assert isinstance(geom.detector, odl.tomo.Flat2dDetector) det_refpoint = geom.det_refpoint(2 * np.pi) assert almost_equal(np.linalg.norm(det_refpoint[0:2]), det_rad) angles = geom.angles num_angles = geom.angles.size src_rad = geom.src_radius det_rad = geom.det_radius pitch = geom.pitch for ang_ind in range(num_angles): angle = angles[ang_ind] z = pitch * angle / (2 * np.pi) # source pnt = (-np.cos(angle) * src_rad, -np.sin(angle) * src_rad, z) assert all_almost_equal(geom.src_position(angle), pnt) # center of detector det = geom.det_refpoint(angle) src = geom.src_position(angle) val0 = np.linalg.norm(src[0:2]) / src_rad val1 = np.linalg.norm(det[0:2]) / det_rad assert almost_equal(val0, val1) assert almost_equal(src[2], det[2]) # check str and repr work without crashing and return something assert str(geom) assert repr(geom)
def test_solver(optimization_problem, iterative_solver): """Test iterative solver for solving some simple problems.""" op, x, rhs = optimization_problem iterative_solver(op, x, rhs) assert all_almost_equal(op(x), rhs, ndigits=2)
def test_ndarray_init(tspace): x0 = np.arange(tspace.size).reshape(tspace.shape) x = tspace.element(x0) assert all_almost_equal(x0, x)
def test_detector_transformations(): """ Tests projecting data from one detector to another using interpolation. Given 2 detectors C and F. If data is sampled from a smooth function, projecting C -> F -> C must be close to an identity mapping. """ # 1D, circular <-> flat r = 3 part = odl.uniform_partition(-np.pi / 4, np.pi / 4, 21, nodes_on_bdry=True) C = odl.tomo.CircularDetector(part, axis=[1, 0], radius=r) F = odl.tomo.curved_to_flat(C) C2 = odl.tomo.flat_to_curved(F, radius=r) assert C.partition == C2.partition assert C.radius == C2.radius # take linear function data0 = part.points().squeeze() data1 = odl.tomo.project_data(data0, C, F) data2 = odl.tomo.project_data(data1, F, C) data3 = odl.tomo.project_data(data2, C, F) assert all_almost_equal(data0, data2, 3) assert all_almost_equal(data1, data3, 3) # 2D, spherical <-> flat r = 5 part = odl.uniform_partition([-np.pi / 4, -np.pi / 4], [np.pi / 4, np.pi / 4], (31, 31), nodes_on_bdry=True) S = odl.tomo.SphericalDetector(part, axes=[[1, 0, 0], [0, 0, 1]], radius=r) F = odl.tomo.curved_to_flat(S) S2 = odl.tomo.flat_to_curved(F, radius=(r, r)) # height is going to increase after projecting to flat detector and back assert all_almost_equal(S.partition.meshgrid[0], S2.partition.meshgrid[0]) assert S.radius == S2.radius # take linear function data0 = np.sum(part.points(), axis=-1).reshape(part.shape) data1 = odl.tomo.project_data(data0, S, F) data2 = odl.tomo.project_data(data1, F, S) data3 = odl.tomo.project_data(data2, S, F) # there is some loss of energy at the upper and lower boundaries, # even with nodes_on_bdry=True # (since the boundary of a spherical detector is not a straight line, # when projected on a flat detector), affects around 30% from each side l = part.shape[1] // 3 u = part.shape[1] - l assert all_almost_equal(data0[:, l:u], data2[:, l:u], 3) assert all_almost_equal(data1[:, l:u], data3[:, l:u], 3) # 2D, flat <-> cylindrical C = odl.tomo.flat_to_curved(F, radius=r) F2 = odl.tomo.curved_to_flat(C) # height is going to increase after projecting to curved detector and back assert all_almost_equal(F.partition.meshgrid[0], F2.partition.meshgrid[0]) data1 = odl.tomo.project_data(data0, F, C) data2 = odl.tomo.project_data(data1, C, F) data3 = odl.tomo.project_data(data2, F, C) assert all_almost_equal(data0[:, l:u], data2[:, l:u], 3) assert all_almost_equal(data1[:, l:u], data3[:, l:u], 3) # 2D, spherical <-> cylindrical data1 = odl.tomo.project_data(data0, S, C) data2 = odl.tomo.project_data(data1, C, S) data3 = odl.tomo.project_data(data2, S, C) assert all_almost_equal(data0[:, l:u], data2[:, l:u], 3) assert all_almost_equal(data1[:, l:u], data3[:, l:u], 3) # testing in the context of tomography if not odl.tomo.ASTRA_CUDA_AVAILABLE: pytest.skip(msg='ASTRA_CUDA not available, skipping 3d test') space = odl.uniform_discr([-1] * 3, [1] * 3, [10] * 3) x = odl.phantom.shepp_logan(space) apart = odl.uniform_partition(0, np.pi, 180) dpart = odl.uniform_partition([-1, -1], [1, 1], (128, 32)) geom_flat = odl.tomo.ConeBeamGeometry(apart, dpart, src_radius=1, det_radius=1) curved_det = odl.tomo.flat_to_curved(geom_flat.detector, radius=30) operator_flat = odl.tomo.RayTransform(space, geom_flat) data_flat = operator_flat(x).asarray() data_curved = odl.tomo.project_data(data_flat, geom_flat.detector, curved_det) data_flat2 = odl.tomo.project_data(data_curved, curved_det, geom_flat.detector) assert all_almost_equal(data_flat, data_flat2, 2)
def test_fspace_elem_real_imag_conj(out_shape): """Check taking real/imaginary parts of fspace elements.""" fspace = FunctionSpace(odl.IntervalProd(0, 1), out_dtype=(complex, out_shape)) ndim = len(out_shape) if ndim == 0: f_elem = fspace.element(func_complex_nd_oop) elif ndim == 1: f_elem = fspace.element(func_vec_complex_nd_oop) elif ndim == 2: f_elem = fspace.element(func_tens_complex_oop) else: assert False points = _points(fspace.domain, 4) mesh_shape = (5, ) mesh = _meshgrid(fspace.domain, mesh_shape) point = 0.5 values_points_shape = out_shape + (4, ) values_mesh_shape = out_shape + mesh_shape result_points = f_elem(points) result_point = f_elem(point) result_mesh = f_elem(mesh) assert all_almost_equal(f_elem.real(points), result_points.real) assert all_almost_equal(f_elem.real(point), result_point.real) assert all_almost_equal(f_elem.real(mesh), result_mesh.real) assert all_almost_equal(f_elem.imag(points), result_points.imag) assert all_almost_equal(f_elem.imag(point), result_point.imag) assert all_almost_equal(f_elem.imag(mesh), result_mesh.imag) assert all_almost_equal(f_elem.conj()(points), result_points.conj()) assert all_almost_equal(f_elem.conj()(point), np.conj(result_point)) assert all_almost_equal(f_elem.conj()(mesh), result_mesh.conj()) out_points = np.empty(values_points_shape, dtype=float) out_mesh = np.empty(values_mesh_shape, dtype=float) f_elem.real(points, out=out_points) f_elem.real(mesh, out=out_mesh) assert all_almost_equal(out_points, result_points.real) assert all_almost_equal(out_mesh, result_mesh.real) f_elem.imag(points, out=out_points) f_elem.imag(mesh, out=out_mesh) assert all_almost_equal(out_points, result_points.imag) assert all_almost_equal(out_mesh, result_mesh.imag) out_points = np.empty(values_points_shape, dtype=complex) out_mesh = np.empty(values_mesh_shape, dtype=complex) f_elem.conj()(points, out=out_points) f_elem.conj()(mesh, out=out_mesh) assert all_almost_equal(out_points, result_points.conj()) assert all_almost_equal(out_mesh, result_mesh.conj())
def test_uniform_discr_fromdiscr_one_attr(): # Change 1 attribute discr = odl.uniform_discr([0, -1], [1, 1], [10, 5]) # csides = [0.1, 0.4] # min_corner -> translate, keep cells new_min_corner = [3, 7] true_new_end = [4, 9] new_discr = odl.uniform_discr_fromdiscr(discr, min_corner=new_min_corner) assert all_almost_equal(new_discr.min_corner, new_min_corner) assert all_almost_equal(new_discr.max_corner, true_new_end) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, discr.cell_sides) # max_corner -> translate, keep cells new_max_corner = [3, 7] true_new_begin = [2, 5] new_discr = odl.uniform_discr_fromdiscr(discr, max_corner=new_max_corner) assert all_almost_equal(new_discr.min_corner, true_new_begin) assert all_almost_equal(new_discr.max_corner, new_max_corner) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, discr.cell_sides) # nsamples -> resize cells, keep corners new_nsamples = (5, 20) true_new_csides = [0.2, 0.1] new_discr = odl.uniform_discr_fromdiscr(discr, nsamples=new_nsamples) assert all_almost_equal(new_discr.min_corner, discr.min_corner) assert all_almost_equal(new_discr.max_corner, discr.max_corner) assert all_equal(new_discr.shape, new_nsamples) assert all_almost_equal(new_discr.cell_sides, true_new_csides) # cell_sides -> resize cells, keep corners new_csides = [0.5, 0.2] true_new_nsamples = (2, 10) new_discr = odl.uniform_discr_fromdiscr(discr, cell_sides=new_csides) assert all_almost_equal(new_discr.min_corner, discr.min_corner) assert all_almost_equal(new_discr.max_corner, discr.max_corner) assert all_equal(new_discr.shape, true_new_nsamples) assert all_almost_equal(new_discr.cell_sides, new_csides)
def test_fanflat_props(shift): """Test basic properties of 2d fanflat geometries.""" full_angle = 2 * np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition(0, 1, 10) src_rad = 10 det_rad = 5 translation = np.array([shift, shift], dtype=float) geom = odl.tomo.FanFlatGeometry(apart, dpart, src_rad, det_rad, translation=translation) assert geom.ndim == 2 assert isinstance(geom.detector, odl.tomo.Flat1dDetector) # Check defaults assert all_almost_equal(geom.src_to_det_init, [0, 1]) assert all_almost_equal(geom.src_position(0), translation + [0, -src_rad]) assert all_almost_equal(geom.det_refpoint(0), translation + [0, det_rad]) assert all_almost_equal(geom.det_point_position(0, 0), geom.det_refpoint(0)) assert all_almost_equal(geom.det_axis_init, [1, 0]) assert all_almost_equal(geom.det_axis(0), [1, 0]) assert all_almost_equal(geom.translation, translation) # Check that we first rotate, then shift along the rotated axis, which # is equivalent to shifting first and then rotating. # Here we expect to rotate the reference point to [-det_rad, 0] and then # shift by 1 (=detector param) along the detector axis [0, 1] at that # angle. # Global translation should come afterwards. assert all_almost_equal(geom.det_point_position(np.pi / 2, 1), translation + [-det_rad, 1]) assert all_almost_equal(geom.det_axis(np.pi / 2), [0, 1]) # Detector to source vector. At param=0 it should be perpendicular to # the detector towards the source, here at pi/2 it should point into # the (+x) direction. # At any other parameter, when adding the non-normalized vector to the # detector point position, one should get the source position. assert all_almost_equal(geom.det_to_src(np.pi / 2, 0), [1, 0]) src_pos = (geom.det_point_position(np.pi / 2, 1) + geom.det_to_src(np.pi / 2, 1, normalized=False)) assert all_almost_equal(src_pos, geom.src_position(np.pi / 2)) # Rotation matrix, should correspond to counter-clockwise rotation assert all_almost_equal(geom.rotation_matrix(np.pi / 2), [[0, -1], [1, 0]]) # Make sure that the boundary cases are treated as valid geom.det_point_position(0, 0) geom.det_point_position(full_angle, 1) # Invalid parameter with pytest.raises(ValueError): geom.rotation_matrix(2 * full_angle) # Both radii zero with pytest.raises(ValueError): odl.tomo.FanFlatGeometry(apart, dpart, src_radius=0, det_radius=0) # Check that str and repr work without crashing and return something assert str(geom) assert repr(geom)
def test_uniform_partition(): min_pt = [0, 0] max_pt = [1, 2] shape = (4, 10) csides = [0.25, 0.2] # Test standard case part = odl.uniform_partition(min_pt, max_pt, shape, nodes_on_bdry=True) assert all_equal(part.min_pt, min_pt) assert all_equal(part.max_pt, max_pt) assert all_equal(part.grid.min_pt, min_pt) assert all_equal(part.grid.max_pt, max_pt) for cs in part.cell_sizes_vecs: # Check that all cell sizes are equal (except first and last which # are halved) assert np.allclose(np.diff(cs[1:-1]), 0) assert all_almost_equal(cs[0], cs[1] / 2) assert all_almost_equal(cs[-1], cs[-2] / 2) assert part[1:, 2:5].is_uniform assert part[1:, ::3].is_uniform # Test combinations of parameters true_part = odl.uniform_partition(min_pt, max_pt, shape, nodes_on_bdry=False) part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, shape=shape, cell_sides=None) assert part == true_part part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, shape=None, cell_sides=csides) assert part == true_part part = odl.uniform_partition(min_pt=min_pt, max_pt=None, shape=shape, cell_sides=csides) assert part == true_part part = odl.uniform_partition(min_pt=None, max_pt=max_pt, shape=shape, cell_sides=csides) assert part == true_part part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, shape=shape, cell_sides=csides) assert part == true_part # Test parameters per axis part = odl.uniform_partition(min_pt=[0, None], max_pt=[None, 2], shape=shape, cell_sides=csides) assert part == true_part part = odl.uniform_partition(min_pt=min_pt, max_pt=[None, 2], shape=(4, None), cell_sides=csides) assert part == true_part part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, shape=(None, 4), cell_sides=[0.25, None]) # Test robustness against numerical error part = odl.uniform_partition(min_pt=min_pt, max_pt=[None, np.sqrt(2)**2], shape=shape, cell_sides=[0.25, np.log(np.exp(0.2))]) assert part.approx_equals(true_part, atol=1e-8) # Test nodes_on_bdry # Here we compute stuff, so we can only expect approximate equality csides = [1 / 3., 2 / 9.5] true_part = odl.uniform_partition(min_pt, max_pt, shape, nodes_on_bdry=(True, (False, True))) part = odl.uniform_partition(min_pt=[0, None], max_pt=[None, 2], shape=shape, cell_sides=csides, nodes_on_bdry=(True, (False, True))) assert part.approx_equals(true_part, atol=1e-8) part = odl.uniform_partition(min_pt=min_pt, max_pt=[None, 2], shape=(4, None), cell_sides=csides, nodes_on_bdry=(True, (False, True))) assert part.approx_equals(true_part, atol=1e-8) part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, shape=(None, 10), cell_sides=[1 / 3., None], nodes_on_bdry=(True, (False, True))) assert part.approx_equals(true_part, atol=1e-8) # Test error scenarios # Not enough parameters (total / per axis) with pytest.raises(ValueError): odl.uniform_partition() with pytest.raises(ValueError): odl.uniform_partition(min_pt, max_pt) with pytest.raises(ValueError): part = odl.uniform_partition(min_pt=[0, None], max_pt=[1, None], shape=shape, cell_sides=csides) with pytest.raises(ValueError): part = odl.uniform_partition(min_pt=min_pt, max_pt=[1, None], shape=(4, None), cell_sides=csides) # Parameters with inconsistent sizes with pytest.raises(ValueError): part = odl.uniform_partition(min_pt=min_pt, max_pt=[1, None, None], shape=shape) # Too large rounding error in computing shape with pytest.raises(ValueError): part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, cell_sides=[0.25, 0.2001]) # Inconsistent values with pytest.raises(ValueError): part = odl.uniform_partition(min_pt=min_pt, max_pt=max_pt, shape=shape, cell_sides=[0.25, 0.2001])
def test_chambolle_pock_solver_simple_space(): """Test for the Chambolle-Pock algorithm.""" # Create a discretized image space space = odl.uniform_discr(0, 1, DATA.size) # Operator op = odl.IdentityOperator(space) # Starting point (image) discr_vec = op.domain.element(DATA) # Relaxation variable required to resume iteration discr_vec_relax = discr_vec.copy() # Dual variable required to resume iteration discr_dual = op.range.zero() # Functional, use the same functional for F^* and G g = odl.solvers.ZeroFunctional(space) f = g.convex_conj # Run the algorithm chambolle_pock_solver(discr_vec, f, g, op, tau=TAU, sigma=SIGMA, theta=THETA, niter=1, callback=None, x_relax=discr_vec_relax, y=discr_dual) # Explicit computation vec_expl = (1 - TAU * SIGMA) * DATA assert all_almost_equal(discr_vec, vec_expl, PLACES) # Explicit computation of the value of the relaxation variable vec_relax_expl = (1 + THETA) * vec_expl - THETA * DATA assert all_almost_equal(discr_vec_relax, vec_relax_expl, PLACES) # Resume iteration with previous x but without previous relaxation chambolle_pock_solver(discr_vec, f, g, op, tau=TAU, sigma=SIGMA, theta=THETA, niter=1) vec_expl *= (1 - SIGMA * TAU) assert all_almost_equal(discr_vec, vec_expl, PLACES) # Resume iteration with x1 as above and with relaxation parameter discr_vec[:] = vec_expl chambolle_pock_solver(discr_vec, f, g, op, tau=TAU, sigma=SIGMA, theta=THETA, niter=1, x_relax=discr_vec_relax, y=discr_dual) vec_expl = vec_expl - TAU * SIGMA * (DATA + vec_relax_expl) assert all_almost_equal(discr_vec, vec_expl, PLACES) # Test acceleration parameter: use output argument for the relaxation # variable since otherwise two iterations are required for the # relaxation to take effect on the input variable # Acceleration parameter gamma=0 corresponds to relaxation parameter # theta=1 without acceleration # Relaxation parameter 1 and no acceleration discr_vec = op.domain.element(DATA) discr_vec_relax_no_gamma = op.domain.element(DATA) chambolle_pock_solver(discr_vec, f, g, op, tau=TAU, sigma=SIGMA, theta=1, gamma=None, niter=1, x_relax=discr_vec_relax_no_gamma) # Acceleration parameter 0, overwrites relaxation parameter discr_vec = op.domain.element(DATA) discr_vec_relax_g0 = op.domain.element(DATA) chambolle_pock_solver(discr_vec, f, g, op, tau=TAU, sigma=SIGMA, theta=0, gamma=0, niter=1, x_relax=discr_vec_relax_g0) assert discr_vec != discr_vec_relax_no_gamma assert all_almost_equal(discr_vec_relax_no_gamma, discr_vec_relax_g0) # Test callback execution chambolle_pock_solver(discr_vec, f, g, op, tau=TAU, sigma=SIGMA, theta=THETA, niter=1, callback=odl.solvers.CallbackPrintIteration())
def test_helical_cone_flat_props(shift): """Test basic properties of 3D helical cone beam geometries.""" full_angle = 2 * np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], (10, 10)) src_rad = 10 det_rad = 5 pitch = 2.0 translation = np.array([shift, shift, shift], dtype=float) geom = odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch=pitch, translation=translation) assert geom.ndim == 3 assert isinstance(geom.detector, odl.tomo.Flat2dDetector) # Check defaults assert all_almost_equal(geom.axis, [0, 0, 1]) assert all_almost_equal(geom.src_to_det_init, [0, 1, 0]) assert all_almost_equal(geom.src_position(0), translation + [0, -src_rad, 0]) assert all_almost_equal(geom.det_refpoint(0), translation + [0, det_rad, 0]) assert all_almost_equal(geom.det_point_position(0, [0, 0]), geom.det_refpoint(0)) assert all_almost_equal(geom.det_axes_init, ([1, 0, 0], [0, 0, 1])) assert all_almost_equal(geom.det_axes(0), ([1, 0, 0], [0, 0, 1])) assert all_almost_equal(geom.translation, translation) # Check that we first rotate, then shift along the initial detector # axes rotated according to the angle, which is equivalent to shifting # first and then rotating. # Here we expect to rotate the reference point to [-det_rad, 0, 0] and # then shift by (1, 1) (=detector param) along the detector axes # ([0, 1, 0], [0, 0, 1]) at that angle. In addition, everything is # shifted along the rotation axis [0, 0, 1] by 1/4 of the pitch # (since the pitch is the vertical distance after a full turn 2*pi). # Global translation should come last. assert all_almost_equal(geom.det_point_position(np.pi / 2, [1, 1]), translation + [-det_rad, 1, 1 + pitch / 4]) # Make sure that source and detector move at the same height and stay # opposite of each other src_to_det_ref = (geom.det_refpoint(np.pi / 2) - geom.src_position(np.pi / 2)) assert np.dot(geom.axis, src_to_det_ref) == pytest.approx(0) assert np.linalg.norm(src_to_det_ref) == pytest.approx(src_rad + det_rad) # Detector to source vector. At param=0 it should be perpendicular to # the detector towards the source, here at pi/2 it should point into # the (+x) direction. # At any other parameter, when adding the non-normalized vector to the # detector point position, one should get the source position. assert all_almost_equal(geom.det_to_src(np.pi / 2, [0, 0]), [1, 0, 0]) src_pos = (geom.det_point_position(np.pi / 2, [1, 1]) + geom.det_to_src(np.pi / 2, [1, 1], normalized=False)) assert all_almost_equal(src_pos, geom.src_position(np.pi / 2)) # Rotation matrix, should correspond to counter-clockwise rotation # arond the z axis assert all_almost_equal(geom.rotation_matrix(np.pi / 2), [[0, -1, 0], [1, 0, 0], [0, 0, 1]]) # offset_along_axis geom = odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch=pitch, offset_along_axis=0.5) assert all_almost_equal(geom.det_refpoint(0), [0, det_rad, 0.5]) # Make sure that the boundary cases are treated as valid geom.det_point_position(0, [0, 0]) geom.det_point_position(full_angle, [1, 1]) # Invalid parameter with pytest.raises(ValueError): geom.rotation_matrix(2 * full_angle) # Zero not allowed as axis with pytest.raises(ValueError): odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch=pitch, axis=[0, 0, 0]) # Detector axex should not be parallel or otherwise result in a # linear dependent triplet with pytest.raises(ValueError): odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch=pitch, det_axes_init=([0, 1, 0], [0, 1, 0])) with pytest.raises(ValueError): odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch=pitch, det_axes_init=([0, 0, 0], [0, 1, 0])) # Both radii zero with pytest.raises(ValueError): odl.tomo.ConeFlatGeometry(apart, dpart, src_radius=0, det_radius=0, pitch=pitch) # Check that str and repr work without crashing and return something assert str(geom) assert repr(geom)
def test_ufunc(impl, ufunc): space = odl.uniform_discr([0, 0], [1, 1], (2, 2), impl=impl) name, n_args, n_out, _ = ufunc if (np.issubsctype(space.dtype, np.floating) and name in ['bitwise_and', 'bitwise_or', 'bitwise_xor', 'invert', 'left_shift', 'right_shift']): # Skip integer only methods if floating point type return # Get the ufunc from numpy as reference ufunc = getattr(np, name) # Create some data arrays, vectors = example_vectors(space, n_args + n_out) in_arrays = arrays[:n_args] out_arrays = arrays[n_args:] data_vector = vectors[0] in_vectors = vectors[1:n_args] out_vectors = vectors[n_args:] # Verify type assert isinstance(data_vector.ufunc, odl.util.ufuncs.DiscreteLpUFuncs) # Out of place: np_result = ufunc(*in_arrays) vec_fun = getattr(data_vector.ufunc, name) odl_result = vec_fun(*in_vectors) assert all_almost_equal(np_result, odl_result) # Test type of output if n_out == 1: assert isinstance(odl_result, space.element_type) elif n_out > 1: for i in range(n_out): assert isinstance(odl_result[i], space.element_type) # In place: np_result = ufunc(*(in_arrays + out_arrays)) vec_fun = getattr(data_vector.ufunc, name) odl_result = vec_fun(*(in_vectors + out_vectors)) assert all_almost_equal(np_result, odl_result) # Test inplace actually holds: if n_out == 1: assert odl_result is out_vectors[0] elif n_out > 1: for i in range(n_out): assert odl_result[i] is out_vectors[i] # Test out of place with np data np_result = ufunc(*in_arrays) vec_fun = getattr(data_vector.ufunc, name) odl_result = vec_fun(*in_arrays[1:]) assert all_almost_equal(np_result, odl_result) # Test type of output if n_out == 1: assert isinstance(odl_result, space.element_type) elif n_out > 1: for i in range(n_out): assert isinstance(odl_result[i], space.element_type)
def test_gradient(space, method, padding): """Discretized spatial gradient operator.""" places = 2 if space.dtype == np.float32 else 4 with pytest.raises(TypeError): Gradient(odl.rn(1), method=method) if isinstance(padding, tuple): pad_mode, pad_const = padding else: pad_mode, pad_const = padding, 0 # DiscreteLp Vector dom_vec = noise_element(space) dom_vec_arr = dom_vec.asarray() # gradient grad = Gradient(space, method=method, pad_mode=pad_mode, pad_const=pad_const) grad_vec = grad(dom_vec) assert len(grad_vec) == space.ndim # computation of gradient components with helper function for axis, dx in enumerate(space.cell_sides): diff = finite_diff(dom_vec_arr, axis=axis, dx=dx, method=method, pad_mode=pad_mode, pad_const=pad_const) assert all_almost_equal(grad_vec[axis].asarray(), diff) # Test adjoint operator derivative = grad.derivative() ran_vec = noise_element(derivative.range) deriv_grad_vec = derivative(dom_vec) adj_grad_vec = derivative.adjoint(ran_vec) lhs = ran_vec.inner(deriv_grad_vec) rhs = dom_vec.inner(adj_grad_vec) # Check not to use trivial data assert lhs != 0 assert rhs != 0 assert almost_equal(lhs, rhs, places=places) # higher dimensional arrays lin_size = 3 for ndim in [1, 3, 6]: # DiscreteLpElement space = odl.uniform_discr([0.] * ndim, [1.] * ndim, [lin_size] * ndim) dom_vec = odl.phantom.cuboid(space, [0.2] * ndim, [0.8] * ndim) # gradient grad = Gradient(space, method=method, pad_mode=pad_mode, pad_const=pad_const) grad(dom_vec)
def test_source_shifts_2d(): """Check that source shifts are handled correctly. We forward project a Shepp-Logan phantom and check that reconstruction with flying focal spot is equal to a sum of reconstructions with two geometries which mimic ffs by using initial angular offsets and detector shifts """ if not odl.tomo.ASTRA_AVAILABLE: pytest.skip(msg='ASTRA required but not available') d = 10 space = odl.uniform_discr([-1] * 2, [1] * 2, [d] * 2) phantom = odl.phantom.cuboid(space, [-1/3] * 2, [1/3] * 2) full_angle = 2 * np.pi n_angles = 2 * 10 src_rad = 2 det_rad = 2 apart = odl.uniform_partition(0, full_angle, n_angles) dpart = odl.uniform_partition(-4, 4, 8 * d) # Source positions with flying focal spot should correspond to # source positions of 2 geometries with different starting positions shift1 = np.array([0.0, -0.3]) shift2 = np.array([0.0, 0.3]) init = np.array([1, 0], dtype=np.float32) det_init = np.array([0, -1], dtype=np.float32) ffs = partial(odl.tomo.flying_focal_spot, apart=apart, shifts=[shift1, shift2]) geom_ffs = odl.tomo.FanBeamGeometry(apart, dpart, src_rad, det_rad, src_to_det_init=init, det_axis_init=det_init, src_shift_func=ffs, det_shift_func=ffs) # angles must be shifted to match discretization of apart ang1 = -full_angle / (n_angles * 2) apart1 = odl.uniform_partition(ang1, full_angle + ang1, n_angles // 2) ang2 = full_angle / (n_angles * 2) apart2 = odl.uniform_partition(ang2, full_angle + ang2, n_angles // 2) init1 = init + np.array([0, shift1[1]]) / (src_rad + shift1[0]) init2 = init + np.array([0, shift2[1]]) / (src_rad + shift2[0]) # radius also changes when a shift is applied src_rad1 = np.linalg.norm(np.array([src_rad, 0]) + shift1) src_rad2 = np.linalg.norm(np.array([src_rad, 0]) + shift2) det_rad1 = np.linalg.norm( np.array([det_rad, shift1[1] / src_rad * det_rad])) det_rad2 = np.linalg.norm( np.array([det_rad, shift2[1] / src_rad * det_rad])) geom1 = odl.tomo.FanBeamGeometry(apart1, dpart, src_rad1, det_rad1, src_to_det_init=init1, det_axis_init=det_init) geom2 = odl.tomo.FanBeamGeometry(apart2, dpart, src_rad2, det_rad2, src_to_det_init=init2, det_axis_init=det_init) # check ray transform op_ffs = odl.tomo.RayTransform(space, geom_ffs) op1 = odl.tomo.RayTransform(space, geom1) op2 = odl.tomo.RayTransform(space, geom2) y_ffs = op_ffs(phantom) y1 = op1(phantom).asarray() y2 = op2(phantom).asarray() assert all_almost_equal(y_ffs[::2], y1) assert all_almost_equal(y_ffs[1::2], y2) # check back-projection im = op_ffs.adjoint(y_ffs).asarray() im1 = op1.adjoint(y1).asarray() im2 = op2.adjoint(y2).asarray() im_combined = (im1 + im2) / 2 rel_error = np.abs((im - im_combined)[im > 0] / im[im > 0]) assert np.max(rel_error) < 1e-6
def test_ufunc_corner_cases(odl_tspace_impl): """Check if some corner cases are handled correctly.""" impl = odl_tspace_impl space = odl.uniform_discr([0, 0], [1, 1], (2, 3), impl=impl) x = space.element([[-1, 0, 1], [1, 2, 3]]) space_no_w = odl.uniform_discr([0, 0], [1, 1], (2, 3), impl=impl, weighting=1.0) # --- UFuncs with nin = 1, nout = 1 --- # with pytest.raises(ValueError): # Too many arguments x.__array_ufunc__(np.sin, '__call__', x, np.ones((2, 3))) # Check that `out=(None,)` is the same as not providing `out` res = x.__array_ufunc__(np.sin, '__call__', x, out=(None, )) assert all_almost_equal(res, np.sin(x.asarray())) # Check that the result space is the same assert res.space == space # Check usage of `order` argument for order in ('C', 'F'): res = x.__array_ufunc__(np.sin, '__call__', x, order=order) assert all_almost_equal(res, np.sin(x.asarray())) assert res.tensor.data.flags[order + '_CONTIGUOUS'] # Check usage of `dtype` argument res = x.__array_ufunc__(np.sin, '__call__', x, dtype=complex) assert all_almost_equal(res, np.sin(x.asarray(), dtype=complex)) assert res.dtype == complex # Check propagation of weightings y = space_no_w.one() res = y.__array_ufunc__(np.sin, '__call__', y) assert res.space.weighting == space_no_w.weighting y = space_no_w.one() res = y.__array_ufunc__(np.sin, '__call__', y) assert res.space.weighting == space_no_w.weighting # --- UFuncs with nin = 2, nout = 1 --- # with pytest.raises(ValueError): # Too few arguments x.__array_ufunc__(np.add, '__call__', x) with pytest.raises(ValueError): # Too many outputs out1, out2 = np.empty_like(x), np.empty_like(x) x.__array_ufunc__(np.add, '__call__', x, x, out=(out1, out2)) # Check that npy_array += odl_vector works arr = np.ones((2, 3)) arr += x assert all_almost_equal(arr, x.asarray() + 1) # For Numpy >= 1.13, this will be equivalent arr = np.ones((2, 3)) res = x.__array_ufunc__(np.add, '__call__', arr, x, out=(arr, )) assert all_almost_equal(arr, x.asarray() + 1) assert res is arr # --- `accumulate` --- # res = x.__array_ufunc__(np.add, 'accumulate', x) assert all_almost_equal(res, np.add.accumulate(x.asarray())) assert res.space == space arr = np.empty_like(x) res = x.__array_ufunc__(np.add, 'accumulate', x, out=(arr, )) assert all_almost_equal(arr, np.add.accumulate(x.asarray())) assert res is arr # `accumulate` with other dtype res = x.__array_ufunc__(np.add, 'accumulate', x, dtype='float32') assert res.dtype == 'float32' # Error scenarios with pytest.raises(ValueError): # Too many `out` arguments out1, out2 = np.empty_like(x), np.empty_like(x) x.__array_ufunc__(np.add, 'accumulate', x, out=(out1, out2)) # --- `reduce` --- # res = x.__array_ufunc__(np.add, 'reduce', x) assert all_almost_equal(res, np.add.reduce(x.asarray())) with pytest.raises(ValueError): x.__array_ufunc__(np.add, 'reduce', x, keepdims=True) # With `out` argument and `axis` out_ax0 = np.empty(3) res = x.__array_ufunc__(np.add, 'reduce', x, axis=0, out=(out_ax0, )) assert all_almost_equal(out_ax0, np.add.reduce(x.asarray(), axis=0)) assert res is out_ax0 out_ax1 = odl.rn(2).element() res = x.__array_ufunc__(np.add, 'reduce', x, axis=1, out=(out_ax1, )) assert all_almost_equal(out_ax1, np.add.reduce(x.asarray(), axis=1)) assert res is out_ax1 # Addition is re-orderable, so we can give multiple axes res = x.__array_ufunc__(np.add, 'reduce', x, axis=(0, 1)) assert res == pytest.approx(np.add.reduce(x.asarray(), axis=(0, 1))) # Constant weighting should be preserved (recomputed from cell # volume) y = space.one() res = y.__array_ufunc__(np.add, 'reduce', y, axis=0) assert res.space.weighting.const == pytest.approx(space.cell_sides[1]) # Check that `exponent` is propagated space_1 = odl.uniform_discr([0, 0], [1, 1], (2, 3), impl=impl, exponent=1) z = space_1.one() res = z.__array_ufunc__(np.add, 'reduce', z, axis=0) assert res.space.exponent == 1 # --- `outer` --- # # Check that weightings are propagated correctly x = y = space.one() res = x.__array_ufunc__(np.add, 'outer', x, y) assert isinstance(res.space.weighting, ConstWeighting) assert res.space.weighting.const == pytest.approx(x.space.weighting.const * y.space.weighting.const) x = space.one() y = space_no_w.one() res = x.__array_ufunc__(np.add, 'outer', x, y) assert isinstance(res.space.weighting, ConstWeighting) assert res.space.weighting.const == pytest.approx(x.space.weighting.const) x = y = space_no_w.one() res = x.__array_ufunc__(np.add, 'outer', x, y) assert not res.space.is_weighted
def test_source_shifts_3d(): """Check that source shifts are handled correctly. We forward project a Shepp-Logan phantom and check that reconstruction with flying focal spot is equal to a sum of reconstructions with two geometries which mimic ffs by using initial angular offsets and detector shifts """ if not odl.tomo.ASTRA_CUDA_AVAILABLE: pytest.skip(msg='ASTRA_CUDA not available, skipping 3d test') d = 10 space = odl.uniform_discr([-1] * 3, [1] * 3, [d] * 3) phantom = odl.phantom.cuboid(space, [-1/3] * 3, [1/3] * 3) full_angle = 2 * np.pi n_angles = 2 * 10 apart = odl.uniform_partition(0, full_angle, n_angles) dpart = odl.uniform_partition([-4] * 2, [4] * 2, [8 * d] * 2) src_rad = 2 det_rad = 2 pitch = 0.2 # Source positions with flying focal spot should correspond to # source positions of 2 geometries with different starting positions shift1 = np.array([0.0, -0.2, 0.1]) shift2 = np.array([0.0, 0.2, -0.1]) init = np.array([1, 0, 0], dtype=np.float32) det_init = np.array([[0, -1, 0], [0, 0, 1]], dtype=np.float32) ffs = partial(odl.tomo.flying_focal_spot, apart=apart, shifts=[shift1, shift2]) geom_ffs = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad, src_to_det_init=init, det_axes_init=det_init, src_shift_func=ffs, det_shift_func=ffs, pitch=pitch) # angles must be shifted to match discretization of apart ang1 = -full_angle / (n_angles * 2) apart1 = odl.uniform_partition(ang1, full_angle + ang1, n_angles // 2) ang2 = full_angle / (n_angles * 2) apart2 = odl.uniform_partition(ang2, full_angle + ang2, n_angles // 2) init1 = init + np.array([0, shift1[1], 0]) / (src_rad + shift1[0]) init2 = init + np.array([0, shift2[1], 0]) / (src_rad + shift2[0]) # radius also changes when a shift is applied src_rad1 = np.linalg.norm(np.array([src_rad + shift1[0], shift1[1], 0])) src_rad2 = np.linalg.norm(np.array([src_rad + shift2[0], shift2[1], 0])) det_rad1 = np.linalg.norm( np.array([det_rad, det_rad / src_rad * shift1[1], 0])) det_rad2 = np.linalg.norm( np.array([det_rad, det_rad / src_rad * shift2[1], 0])) geom1 = odl.tomo.ConeBeamGeometry(apart1, dpart, src_rad1, det_rad1, src_to_det_init=init1, det_axes_init=det_init, offset_along_axis=shift1[2], pitch=pitch) geom2 = odl.tomo.ConeBeamGeometry(apart2, dpart, src_rad2, det_rad2, src_to_det_init=init2, det_axes_init=det_init, offset_along_axis=shift2[2], pitch=pitch) assert all_almost_equal(geom_ffs.src_position(geom_ffs.angles)[::2], geom1.src_position(geom1.angles)) assert all_almost_equal(geom_ffs.src_position(geom_ffs.angles)[1::2], geom2.src_position(geom2.angles)) assert all_almost_equal(geom_ffs.det_refpoint(geom_ffs.angles)[::2], geom1.det_refpoint(geom1.angles)) assert all_almost_equal(geom_ffs.det_refpoint(geom_ffs.angles)[1::2], geom2.det_refpoint(geom2.angles)) assert all_almost_equal(geom_ffs.det_axes(geom_ffs.angles)[::2], geom1.det_axes(geom1.angles)) assert all_almost_equal(geom_ffs.det_axes(geom_ffs.angles)[1::2], geom2.det_axes(geom2.angles)) op_ffs = odl.tomo.RayTransform(space, geom_ffs) op1 = odl.tomo.RayTransform(space, geom1) op2 = odl.tomo.RayTransform(space, geom2) y_ffs = op_ffs(phantom) y1 = op1(phantom) y2 = op2(phantom) assert all_almost_equal(np.mean(y_ffs[::2], axis=(1, 2)), np.mean(y1, axis=(1, 2))) assert all_almost_equal(np.mean(y_ffs[1::2], axis=(1, 2)), np.mean(y2, axis=(1, 2))) im = op_ffs.adjoint(y_ffs).asarray() im_combined = (op1.adjoint(y1).asarray() + op2.adjoint(y2).asarray()) # the scaling is a bit off for older versions of astra im_combined = im_combined / np.sum(im_combined) * np.sum(im) rel_error = np.abs((im - im_combined)[im > 0] / im[im > 0]) assert np.max(rel_error) < 1e-6
def test_parallel_3d_props(shift): """Test basic properties of 3D parallel geometries.""" full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], (10, 10)) translation = np.array([shift, shift, shift], dtype=float) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, translation=translation) assert geom.ndim == 3 assert isinstance(geom.detector, odl.tomo.Flat2dDetector) # Check defaults assert all_almost_equal(geom.axis, [0, 0, 1]) assert all_almost_equal(geom.det_pos_init, translation + [0, 1, 0]) assert all_almost_equal(geom.det_refpoint(0), geom.det_pos_init) assert all_almost_equal(geom.det_point_position(0, [0, 0]), geom.det_pos_init) assert all_almost_equal(geom.det_axes_init, ([1, 0, 0], [0, 0, 1])) assert all_almost_equal(geom.det_axes(0), ([1, 0, 0], [0, 0, 1])) assert all_almost_equal(geom.translation, translation) # Check that we first rotate, then shift along the initial detector # axes rotated according to the angle, which is equivalent to shifting # first and then rotating. # Here we expect to rotate the reference point to [-1, 0, 0] and then # shift by (1, 1) (=detector param) along the detector axes # ([0, 1, 0], [0, 0, 1]) at that angle. # Global translation should come last. assert all_almost_equal(geom.det_point_position(np.pi / 2, [1, 1]), translation + [-1, 1, 1]) # Detector to source vector, should be independent of the detector # parameter. At pi/2 it should point into the (+x) direction. assert all_almost_equal(geom.det_to_src(np.pi / 2, [0, 0]), [1, 0, 0]) assert all_almost_equal(geom.det_to_src(np.pi / 2, [1, 1]), [1, 0, 0]) # Rotation matrix, should correspond to counter-clockwise rotation # arond the z axis assert all_almost_equal(geom.rotation_matrix(np.pi / 2), [[0, -1, 0], [1, 0, 0], [0, 0, 1]]) # Make sure that the boundary cases are treated as valid geom.det_point_position(0, [0, 0]) geom.det_point_position(full_angle, [1, 1]) # Invalid parameter with pytest.raises(ValueError): geom.rotation_matrix(2 * full_angle) # Zero not allowed as axis with pytest.raises(ValueError): odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[0, 0, 0]) # Detector axex should not be parallel or otherwise result in a # linear dependent triplet with pytest.raises(ValueError): odl.tomo.Parallel3dAxisGeometry(apart, dpart, det_axes_init=([0, 1, 0], [0, 1, 0])) with pytest.raises(ValueError): odl.tomo.Parallel3dAxisGeometry(apart, dpart, det_axes_init=([0, 0, 0], [0, 1, 0])) # Check that str and repr work without crashing and return something assert str(geom) assert repr(geom)
def test_dwt(): # Verify that the operator works as axpected # 1D test n = 16 x = np.zeros(n) x[5:10] = 1 wbasis = pywt.Wavelet('db1') nscales = 2 mode = 'sym' size_list = coeff_size_list((n,), nscales, wbasis, mode) # Define a discretized domain domain = odl.FunctionSpace(odl.Interval([-1], [1])) nPoints = np.array([n]) disc_domain = odl.uniform_discr_fromspace(domain, nPoints) disc_phantom = disc_domain.element(x) # Create the discrete wavelet transform operator. # Only the domain of the operator needs to be defined Wop = WaveletTransform(disc_domain, nscales, wbasis, mode) # Compute the discrete wavelet transform of discrete imput image coeffs = Wop(disc_phantom) # Determine the correct range for Wop and verify that coeffs # is an element of it ran_size = np.prod(size_list[0]) ran_size += sum(np.prod(shape) for shape in size_list[1:-1]) disc_range = disc_domain.dspace_type(ran_size, dtype=disc_domain.dtype) assert coeffs in disc_range # Compute the inverse wavelet transform reconstruction1 = Wop.inverse(coeffs) # With othogonal wavelets the inverse is the adjoint reconstruction2 = Wop.adjoint(coeffs) # Verify that the output of Wop.inverse and Wop.adjoint are the same assert all_almost_equal(reconstruction1.asarray(), reconstruction2.asarray()) # Verify that reconstructions lie in correct discretized domain assert reconstruction1 in disc_domain assert reconstruction2 in disc_domain assert all_almost_equal(reconstruction1.asarray(), x) assert all_almost_equal(reconstruction2.asarray(), x) # --------------------------------------------------------------- # 2D test n = 16 x = np.zeros((n, n)) x[5:10, 5:10] = 1 wbasis = pywt.Wavelet('db1') nscales = 2 mode = 'sym' size_list = coeff_size_list((n, n), nscales, wbasis, mode) # Define a discretized domain domain = odl.FunctionSpace(odl.Rectangle([-1, -1], [1, 1])) nPoints = np.array([n, n]) disc_domain = odl.uniform_discr_fromspace(domain, nPoints) disc_phantom = disc_domain.element(x) # Create the discrete wavelet transform operator. # Only the domain of the operator needs to be defined Wop = WaveletTransform(disc_domain, nscales, wbasis, mode) # Compute the discrete wavelet transform of discrete imput image coeffs = Wop(disc_phantom) # Determine the correct range for Wop and verify that coeffs # is an element of it ran_size = np.prod(size_list[0]) ran_size += sum(3 * np.prod(shape) for shape in size_list[1:-1]) disc_range = disc_domain.dspace_type(ran_size, dtype=disc_domain.dtype) assert coeffs in disc_range # Compute the inverse wavelet transform reconstruction1 = Wop.inverse(coeffs) # With othogonal wavelets the inverse is the adjoint reconstruction2 = Wop.adjoint(coeffs) # Verify that the output of Wop.inverse and Wop.adjoint are the same assert all_almost_equal(reconstruction1.asarray(), reconstruction2.asarray()) # Verify that reconstructions lie in correct discretized domain assert reconstruction1 in disc_domain assert reconstruction2 in disc_domain assert all_almost_equal(reconstruction1.asarray(), x) assert all_almost_equal(reconstruction2.asarray(), x) # ------------------------------------------------------------- # 3D test n = 16 x = np.zeros((n, n, n)) x[5:10, 5:10, 5:10] = 1 wbasis = pywt.Wavelet('db2') nscales = 1 mode = 'per' size_list = coeff_size_list((n, n, n), nscales, wbasis, mode) # Define a discretized domain domain = odl.FunctionSpace(odl.Cuboid([-1, -1, -1], [1, 1, 1])) nPoints = np.array([n, n, n]) disc_domain = odl.uniform_discr_fromspace(domain, nPoints) disc_phantom = disc_domain.element(x) # Create the discrete wavelet transform operator related to 3D transform. Wop = WaveletTransform(disc_domain, nscales, wbasis, mode) # Compute the discrete wavelet transform of discrete imput image coeffs = Wop(disc_phantom) # Determine the correct range for Wop and verify that coeffs # is an element of it ran_size = np.prod(size_list[0]) ran_size += sum(7 * np.prod(shape) for shape in size_list[1:-1]) disc_range = disc_domain.dspace_type(ran_size, dtype=disc_domain.dtype) assert coeffs in disc_range # Compute the inverse wavelet transform reconstruction1 = Wop.inverse(coeffs) # With othogonal wavelets the inverse is the adjoint reconstruction2 = Wop.adjoint(coeffs) # Verify that the output of Wop.inverse and Wop.adjoint are the same assert all_almost_equal(reconstruction1, reconstruction2) # Verify that reconstructions lie in correct discretized domain assert reconstruction1 in disc_domain assert reconstruction2 in disc_domain assert all_almost_equal(reconstruction1.asarray(), x) assert all_almost_equal(reconstruction2, disc_phantom)
def test_one(fn): assert all_almost_equal(fn.one(), [1] * fn.size)
def test_kullback_leibler(space): """Test the kullback leibler functional and its convex conjugate.""" # The prior needs to be positive prior = noise_element(space) prior = np.abs(prior) func = odl.solvers.KullbackLeibler(space, prior) # The fucntional is only defined for positive elements x = noise_element(space) x = np.abs(x) one_elem = space.one() # Evaluation of the functional expected_result = ((x - prior + prior * np.log(prior / x)) .inner(one_elem)) assert pytest.approx(func(x), expected_result) # Check property for prior assert all_almost_equal(func.prior, prior) # For elements with (a) negative components it should return inf x_neg = noise_element(space) x_neg = x_neg - x_neg.ufuncs.max() assert func(x_neg) == np.inf # The gradient expected_result = 1 - prior / x assert all_almost_equal(func.gradient(x), expected_result) # The proximal operator sigma = np.random.rand() expected_result = odl.solvers.proximal_cconj( odl.solvers.proximal_cconj_kl(space, g=prior))(sigma)(x) assert all_almost_equal(func.proximal(sigma)(x), expected_result) # The convex conjugate functional cc_func = func.convex_conj assert isinstance(cc_func, KullbackLeiblerConvexConj) # The convex conjugate functional is only finite for elements with all # components smaller than 1. x = noise_element(space) x = x - x.ufuncs.max() + 0.99 # Evaluation of convex conjugate expected_result = - (prior * np.log(1 - x)).inner(one_elem) assert pytest.approx(cc_func(x), expected_result) x_wrong = noise_element(space) x_wrong = x_wrong - x_wrong.ufuncs.max() + 1.01 assert cc_func(x_wrong) == np.inf # The gradient of the convex conjugate expected_result = prior / (1 - x) assert all_almost_equal(cc_func.gradient(x), expected_result) # The proximal of the convex conjugate expected_result = 0.5 * (1 + x - np.sqrt((x - 1)**2 + 4 * sigma * prior)) assert all_almost_equal(cc_func.proximal(sigma)(x), expected_result) # The biconjugate, which is the functional itself since it is proper, # convex and lower-semicontinuous cc_cc_func = cc_func.convex_conj # Check that they evaluate the same assert pytest.approx(cc_cc_func(x), func(x))
def test_uniform_discr_fromdiscr_two_attrs(): # Change 2 attributes -> resize and translate discr = odl.uniform_discr([0, -1], [1, 1], [10, 5]) # csides = [0.1, 0.4] new_min_pt = [-2, 1] new_max_pt = [4, 2] true_new_csides = [0.6, 0.2] new_discr = odl.uniform_discr_fromdiscr(discr, min_pt=new_min_pt, max_pt=new_max_pt) assert all_almost_equal(new_discr.min_pt, new_min_pt) assert all_almost_equal(new_discr.max_pt, new_max_pt) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, true_new_csides) new_min_pt = [-2, 1] new_shape = (5, 20) true_new_max_pt = [-1.5, 9] new_discr = odl.uniform_discr_fromdiscr(discr, min_pt=new_min_pt, shape=new_shape) assert all_almost_equal(new_discr.min_pt, new_min_pt) assert all_almost_equal(new_discr.max_pt, true_new_max_pt) assert all_equal(new_discr.shape, new_shape) assert all_almost_equal(new_discr.cell_sides, discr.cell_sides) new_min_pt = [-2, 1] new_csides = [0.6, 0.2] true_new_max_pt = [4, 2] new_discr = odl.uniform_discr_fromdiscr(discr, min_pt=new_min_pt, cell_sides=new_csides) assert all_almost_equal(new_discr.min_pt, new_min_pt) assert all_almost_equal(new_discr.max_pt, true_new_max_pt) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, new_csides) new_max_pt = [4, 2] new_shape = (5, 20) true_new_min_pt = [3.5, -6] new_discr = odl.uniform_discr_fromdiscr(discr, max_pt=new_max_pt, shape=new_shape) assert all_almost_equal(new_discr.min_pt, true_new_min_pt) assert all_almost_equal(new_discr.max_pt, new_max_pt) assert all_equal(new_discr.shape, new_shape) assert all_almost_equal(new_discr.cell_sides, discr.cell_sides) new_max_pt = [4, 2] new_csides = [0.6, 0.2] true_new_min_pt = [-2, 1] new_discr = odl.uniform_discr_fromdiscr(discr, max_pt=new_max_pt, cell_sides=new_csides) assert all_almost_equal(new_discr.min_pt, true_new_min_pt) assert all_almost_equal(new_discr.max_pt, new_max_pt) assert all_equal(new_discr.shape, discr.shape) assert all_almost_equal(new_discr.cell_sides, new_csides)
def test_kullback_leibler_cross_entorpy(space): """Test the kullback leibler cross entropy and its convex conjugate.""" # The prior needs to be positive prior = noise_element(space) prior = np.abs(prior) func = odl.solvers.KullbackLeiblerCrossEntropy(space, prior) # The fucntional is only defined for positive elements x = noise_element(space) x = np.abs(x) one_elem = space.one() # Evaluation of the functional expected_result = ((prior - x + x * np.log(x / prior)) .inner(one_elem)) assert pytest.approx(func(x), expected_result) # Check property for prior assert all_almost_equal(func.prior, prior) # For elements with (a) negative components it should return inf x_neg = noise_element(space) x_neg = x_neg - x_neg.ufuncs.max() assert func(x_neg) == np.inf # The gradient expected_result = np.log(x / prior) assert all_almost_equal(func.gradient(x), expected_result) # The proximal operator sigma = np.random.rand() expected_result = odl.solvers.proximal_cconj( odl.solvers.proximal_cconj_kl_cross_entropy(space, g=prior))(sigma)(x) assert all_almost_equal(func.proximal(sigma)(x), expected_result) # The convex conjugate functional cc_func = func.convex_conj assert isinstance(cc_func, KullbackLeiblerCrossEntropyConvexConj) # The convex conjugate functional is defined for all values of x. x = noise_element(space) # Evaluation of convex conjugate expected_result = (prior * (np.exp(x) - 1)).inner(one_elem) assert pytest.approx(cc_func(x), expected_result) # The gradient of the convex conjugate expected_result = prior * np.exp(x) assert all_almost_equal(cc_func.gradient(x), expected_result) # The proximal of the convex conjugate expected_result = x - scipy.special.lambertw(sigma * prior * np.exp(x)) assert all_almost_equal(cc_func.proximal(sigma)(x), expected_result) # The biconjugate, which is the functional itself since it is proper, # convex and lower-semicontinuous cc_cc_func = cc_func.convex_conj # Check that they evaluate the same assert pytest.approx(cc_cc_func(x), func(x))
def check_shifts(ffs, shifts): i = 0 while i < part_angles.size: j = min(len(ffs), i + len(shifts)) assert all_almost_equal(ffs[i:j], shifts[:(j - i)]) i = j