def tgv(operator, data, alpha, beta, grad=None, nonneg=True, datafit=None): space = operator.domain if grad is None: grad = gradient(space) E = symm_derivative(space) I = odl.IdentityOperator(grad.range) A1 = odl.ReductionOperator(operator, odl.ZeroOperator(grad.range, operator.range)) A2 = odl.ReductionOperator(grad, -I) A3 = odl.ReductionOperator(odl.ZeroOperator(space, E.range), E) A = odl.BroadcastOperator(*[A1, A2, A3]) F1 = get_data_fit(datafit, data) F2 = alpha * odl.solvers.GroupL1Norm(grad.range) F3 = alpha * beta * odl.solvers.GroupL1Norm(E.range) F = odl.solvers.SeparableSum(F1, F2, F3) if nonneg: G = odl.solvers.SeparableSum(odl.solvers.ZeroFunctional(space), odl.solvers.ZeroFunctional(E.domain)) else: G = odl.solvers.SeparableSum(odl.solvers.IndicatorNonnegativity(space), odl.solvers.ZeroFunctional(E.domain)) return G, F, A
def mri_head_reco_op_4_channel(): """Reconstruction operator for 4 channel MRI of a head. This is a T2 weighted TSE scan of a healthy volunteer. The reconstruction operator is the sum of the modulus of each channel. See the data source with DOI `10.5281/zenodo.800525`_ or the `project webpage`_ for further information. See Also -------- mri_head_data_4_channel References ---------- .. _10.5281/zenodo.800525: https://zenodo.org/record/800525 .. _project webpage: http://imsc.uni-graz.at/mobis/internal/\ platform_aktuell.html """ # To get the same rotation as in the reference article space = odl.uniform_discr(min_pt=[-115.2, -115.2], max_pt=[115.2, 115.2], shape=[256, 256], dtype=complex) trafo = odl.trafos.FourierTransform(space) return odl.ReductionOperator(odl.ComplexModulus(space) * trafo.inverse, 4)
def mri_knee_reco_op_8_channel(): """Reconstruction operator for 8 channel MRI of a knee. This is an SE measurement of the knee of a healthy volunteer. The reconstruction operator is the sum of the modulus of each channel. See the data source with DOI `10.5281/zenodo.800529`_ or the `project webpage`_ for further information. See Also -------- mri_knee_data_8_channel References ---------- .. _10.5281/zenodo.800529: https://zenodo.org/record/800529 .. _project webpage: http://imsc.uni-graz.at/mobis/internal/\ platform_aktuell.html """ # To get the same rotation as in the reference article space = odl.uniform_discr(min_pt=[-74.88, -74.88], max_pt=[74.88, 74.88], shape=[192, 192], dtype=complex) trafo = odl.trafos.FourierTransform(space) return odl.ReductionOperator(odl.ComplexModulus(space) * trafo.inverse, 8)
def get_fbp(A, use_2D=False): if use_2D: fbp = odl.tomo.fbp_op(A, padding=True, filter_type='Hamming', frequency_scaling=0.8) else: # This only works for A a BroadcastOperator, and as long as the # Tam-Danielsson window is a vector/matrix and not an operator if isinstance(A[0], odl.OperatorLeftVectorMult): ops = [Ai.operator for Ai in A] vecs = [Ai.vector for Ai in A] else: ops = A vecs = [ odl.tomo.tam_danielson_window(Ai, smoothing_width=0.1, n_half_rot=3) for Ai in A ] fbp = odl.ReductionOperator(*[ ( odl.tomo.fbp_op( opi, padding=True, filter_type='Hamming', # Hann frequency_scaling=0.8) * wi) for opi, wi in zip(ops, vecs) ]) return fbp
def derivative(self, x): Ax = self(x) tmp = self.range.element() scale0 = self.range.zero() scale1 = self.range.zero() for I, E in zip(self.spectrum, self.energies): tmp.lincomb(1, Ax, -mu0(E), x[0]) tmp.lincomb(1, tmp, -mu1(E), x[1]) tmp.ufunc.exp(out=tmp) scale0.lincomb(1, scale0, I * mu0(E), tmp) scale1.lincomb(1, scale1, I * mu1(E), tmp) return odl.ReductionOperator(odl.MultiplyOperator(scale0), odl.MultiplyOperator(scale1))
gradient = odl.Gradient(reco_space, method='forward') gradient_back = odl.Gradient(reco_space, method='backward') eps = odl.DiagonalOperator(gradient_back, reco_space.ndim) # Create the domain of the problem, given by the reconstruction space and the # range of the gradient on the reconstruction space. domain = odl.ProductSpace(reco_space, gradient.range) # Column vector of three operators defined as: # 1. Computes ``A(x)`` # 2. Computes ``grad(x) - y`` # 3. Computes ``eps(y)`` op = odl.BroadcastOperator( ray_trafo * odl.ComponentProjection(domain, 0), odl.ReductionOperator(gradient, odl.ScalingOperator(gradient.range, -1)), eps * odl.ComponentProjection(domain, 1)) # Do not use the g functional, set it to zero. g = odl.solvers.ZeroFunctional(op.domain) # Create functionals for the dual variable # l2-squared data matching l2_norm = odl.solvers.L2NormSquared(ray_trafo.range).translated(data) # The l1-norms scaled by regularization paramters l1_norm_1 = 0.001 * odl.solvers.L1Norm(gradient.range) l1_norm_2 = 1e-4 * odl.solvers.L1Norm(eps.range) # Combine functionals, order must correspond to the operator K
for e in ellipses]) phantom = phantom.ufunc.absolute() elif phantom_type == 'circles': ellipses = [[1, 0.8, 0.8, 0, 0, 0], [1, 0.4, 0.4, 0.2, 0.2, 0]] domain = odl.ProductSpace(space, len(ellipses)) phantom = domain.element() phantom[0] = odl.phantom.ellipse_phantom(space, [ellipses[0]]) phantom[1] = odl.phantom.ellipse_phantom(space, [ellipses[1]]) phantom[0] -= phantom[1] phantom.show('phantom', indices=np.s_[:]) diagop = odl.DiagonalOperator(ray_trafo, domain.size) redop = odl.ReductionOperator(ray_trafo, domain.size) # gradient grad = odl.Gradient(ray_trafo.domain) grad_n = odl.DiagonalOperator(grad, domain.size) # Create data data = diagop(phantom) data_sum = redop(phantom) # Add noise to data scale_poisson = 1 / np.mean(data) # 1 quanta per pixel, on avg data += odl.phantom.poisson_noise(data * scale_poisson) / scale_poisson scale_white_noise = 0.1 * np.mean(data_sum) # 10% white noise data_sum += odl.phantom.white_noise(data_sum.space) * scale_white_noise
insert_discr2 = odl.uniform_discr_fromdiscr(fine_discr, min_pt=insert_min_pt2, max_pt=insert_max_pt2, cell_sides=fine_discr.cell_sides) # Ray trafo on the insert discretization only insert_ray_trafo1 = odl.tomo.RayTransform(insert_discr1, geometry, impl='astra_cuda') insert_ray_trafo2 = odl.tomo.RayTransform(insert_discr2, geometry, impl='astra_cuda') # Forward operator = sum of masked coarse ray trafo and insert ray trafo sum_ray_trafo = odl.ReductionOperator(masked_coarse_ray_trafo, insert_ray_trafo1, insert_ray_trafo2) # Make phantom in the product space pspace = sum_ray_trafo.domain reco = sum_ray_trafo.domain.zero() data = sum_ray_trafo.range.element(sino * 1000) # %% Reconstruction callback = odl.solvers.CallbackShow() odl.solvers.conjugate_gradient_normal(sum_ray_trafo, reco, data, niter=10, callback=callback)
# [[Dx, 0], [0, Dy], [0.5*Dy, 0.5*Dx]], range=W) E = odl.operator.ProductSpaceOperator( [[Dx, 0], [0, Dy], [0.5*Dy, 0.5*Dx], [0.5*Dy, 0.5*Dx]]) W = E.range # Create the domain of the problem, given by the reconstruction space and the # range of the gradient on the reconstruction space. domain = odl.ProductSpace(U, V) # Column vector of three operators defined as: # 1. Computes ``Ax`` # 2. Computes ``Gx - y`` # 3. Computes ``Ey`` op = odl.BroadcastOperator( A * odl.ComponentProjection(domain, 0), odl.ReductionOperator(G, odl.ScalingOperator(V, -1)), E * odl.ComponentProjection(domain, 1)) # Do not use the g functional, set it to zero. g = odl.solvers.ZeroFunctional(domain) # l2-squared data matching l2_norm = odl.solvers.L2NormSquared(A.range).translated(data) # parameters alpha = 1e-1 beta = 1 # The l1-norms scaled by regularization paramters l1_norm_1 = alpha * odl.solvers.L1Norm(V) l1_norm_2 = alpha * beta * odl.solvers.L1Norm(W)
# Mask coarse_mask = multigrid.operators.MaskingOperator(coarse_discr, fine_min, fine_max) ray_trafo_coarse = odl.tomo.RayTransform(coarse_discr, geometry, impl='astra_cuda') masked_ray_trafo_coarse = ray_trafo_coarse * coarse_mask # Phantom phantom_c = odl.phantom.shepp_logan(coarse_discr, modified=True) phantom_f = odl.phantom.shepp_logan(fine_discr, modified=True) # Ray trafo ray_trafo_fine = odl.tomo.RayTransform(fine_discr, geometry, impl='astra_cuda') pspace_ray_trafo = odl.ReductionOperator(masked_ray_trafo_coarse, ray_trafo_fine) pspace = pspace_ray_trafo.domain phantom = pspace.element([phantom_c, phantom_f]) data = pspace_ray_trafo([phantom_c, phantom_f]) data.show('data') noisy_data = data + odl.phantom.white_noise(ray_trafo_coarse.range, stddev=0.1) noisy_data.show('noisy data') reco = pspace_ray_trafo.domain.zero() multigrid.graphics.show_both(phantom_c, phantom_f) # %% Reconstruction reco_method = 'TV'
wave_op = wave_op / wave_op.norm(estimate=True) # Functionals sol_space = space ** 2 l1norm_wave = odl.solvers.L1Norm(wave_op.range) l1norm_shear = odl.solvers.L1Norm(shear_op.range) data_matching = 1000 * odl.solvers.L2NormSquared(space) data_matching = data_matching.translated(noisy_data) f = odl.solvers.ZeroFunctional(sol_space) penalizer = odl.solvers.SeparableSum(0.05 * l1norm_wave, l1norm_shear) # Forward operators sum_op = odl.ReductionOperator( odl.IdentityOperator(space), 2) coeff_op = odl.DiagonalOperator(wave_op, shear_op) # Solve using douglas_rachford_pd g = [data_matching, penalizer] L = [sum_op, coeff_op] tau = 1 opnorms = [odl.power_method_opnorm(op) for op in L] sigma = [1 / opnorm ** 2 for opnorm in opnorms] callback = odl.solvers.CallbackShow(step=10) images = sol_space.zero() odl.solvers.douglas_rachford_pd( images, f, g, L, tau, sigma, niter=1000,
# This file is part of ODL. # # This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. from __future__ import division import pytest import odl from odl.util.testutils import all_almost_equal, simple_fixture base_op = simple_fixture('base_op', [ odl.IdentityOperator(odl.rn(3)), odl.BroadcastOperator(odl.IdentityOperator(odl.rn(3)), 2), odl.ReductionOperator(odl.IdentityOperator(odl.rn(3)), 2), odl.DiagonalOperator(odl.IdentityOperator(odl.rn(3)), 2), ], fmt=' {name}={value.__class__.__name__}') def test_pspace_op_init(base_op): """Test initialization with different base operators.""" A = base_op op = odl.ProductSpaceOperator([[A]]) assert op.domain == A.domain**1 assert op.range == A.range**1 op = odl.ProductSpaceOperator([[A, A]]) assert op.domain == A.domain**2
# S2 = (alpha2 * L12-Norm): X2^3 -> R, for isotropic TV # Operators # A = broadcasting forward operator: X1 x X2 -> Y # G1 = spatial gradient: X1 -> X1^2 # G2 = spatial gradient: X2 -> X2^2 # B1 = G1 extended to X1 x X2, B1(f1, f2) = G1(f1) # B2 = G2 extended to X1 x X2, B2(f1, f2) = G2(f2) R1 = odl.tomo.RayTransform(X1, geometry) R2 = odl.tomo.RayTransform(X2, geometry) M = odl_multigrid.MaskingOperator(X1, roi_min_pt, roi_max_pt) A1 = R1 * M A2 = R2 A = odl.ReductionOperator(A1, A2) G1 = odl.Gradient(X1, pad_mode='symmetric') G2 = odl.Gradient(X2, pad_mode='order1') alpha1 = 1e0 alpha2 = 5e-2 S1 = alpha1 * odl.solvers.L2NormSquared(G1.range) S2 = alpha2 * odl.solvers.GroupL1Norm(G2.range) B1 = G1 * odl.ComponentProjection(X1 * X2, 0) B2 = G2 * odl.ComponentProjection(X1 * X2, 1) D = odl.solvers.L2NormSquared(A.range).translated(y) # We check how much we gained regarding runtimes:
space_detail = odl.uniform_discr(detail_min_pt, detail_max_pt, detail_shape) ray_trafo_detail = odl.tomo.RayTransform(space_detail, geometry, impl='astra_cuda') # Masking operator for the detail part in the low-resolution space mask = multigrid.operators.MaskingOperator(space_lowres, detail_min_pt, detail_max_pt) ray_trafo_lowres = odl.tomo.RayTransform(space_lowres, geometry, impl='astra_cuda') ray_trafo_lowres_masked = ray_trafo_lowres * mask # Combine both ray transforms via summation (=reduction) ray_trafo_combined = odl.ReductionOperator(ray_trafo_lowres_masked, ray_trafo_detail) pspace = ray_trafo_combined.domain # %% Reconstruction reco_method = 'TV' timing = True if reco_method == 'CG': callback = (odl.solvers.CallbackPrintIteration(step=2) & odl.solvers.CallbackShow(step=2)) niter_cg = 10 reco = pspace.zero() if timing: callback = None with odl.util.Timer(reco_method): odl.solvers.conjugate_gradient_normal(ray_trafo_combined,