Note
Go to the end to download the full example code.
DePierro’s algorithm to optimize the Poisson logL with quadratic intensity prior
This example demonstrates the use of DePierro’s algorithm to minimize the negative Poisson log-likelihood function combined with a quadratic intensity prior:
subject to
using the linear forward model
Tip
parallelproj is python array API compatible meaning it supports different
array backends (e.g. numpy, cupy, torch, …) and devices (CPU or GPU).
Choose your preferred array API xp and device dev below.
31 import array_api_compat.numpy as xp
32
33 # import array_api_compat.cupy as xp
34 # import array_api_compat.torch as xp
35
36 import parallelproj
37 from array_api_compat import to_device
38 import array_api_compat.numpy as np
39 import matplotlib.pyplot as plt
40
41 # choose a device (CPU or CUDA GPU)
42 if "numpy" in xp.__name__:
43 # using numpy, device must be cpu
44 dev = "cpu"
45 elif "cupy" in xp.__name__:
46 # using cupy, only cuda devices are possible
47 dev = xp.cuda.Device(0)
48 elif "torch" in xp.__name__:
49 # using torch valid choices are 'cpu' or 'cuda'
50 dev = "cuda"
Setup of the forward model \(\bar{y}(x) = A x + s\)
We setup a minimal linear forward operator \(A\) respresented by a 4x4 matrix and an arbritrary contamination vector \(s\) of length 4.
Note
The OSEM implementation below works with all linear operators that
subclass LinearOperator (e.g. the high-level projectors).
64 # setup an arbitrary 4x4 matrix
65 mat = xp.asarray(
66 [
67 [2.5, 1.2, 0.3, 0.1],
68 [0.4, 3.1, 0.7, 0.2],
69 [0.1, 0.3, 4.1, 2.5],
70 [0.2, 0.5, 0.2, 0.9],
71 [0.3, 0.1, 0.7, 0.2],
72 ],
73 dtype=xp.float64,
74 device=dev,
75 )
76
77 op_A = parallelproj.MatrixOperator(mat)
78 # setup an arbitrary contamination vector that has shape op_A.out_shape
79 contamination = xp.asarray([0.3, 0.2, 0.1, 0.4, 0.1], dtype=xp.float64, device=dev)
Setup of ground truth and data simulation
We setup an arbitrary ground truth \(x_{true}\) and simulate noise-free and noisy data \(y\) by adding Poisson noise.
88 # ground truth
89 x_true = xp.asarray([5.5, 10.7, 8.2, 7.9], dtype=xp.float64, device=dev)
90
91 # simulated noise-free data
92 noise_free_data = op_A(x_true) + contamination
93
94 # add Poisson noise
95 np.random.seed(1)
96 y = xp.asarray(
97 np.random.poisson(parallelproj.to_numpy_array(noise_free_data)),
98 device=dev,
99 dtype=xp.float64,
100 )
104 x_prior = xp.full(op_A.in_shape, xp.min(x_true), dtype=xp.float64, device=dev)
105 beta = 0.3
106 num_iter = 50
107
108 # initialize x
109 x = xp.ones(op_A.in_shape, dtype=xp.float64, device=dev)
113 def cost_function(x):
114 exp = op_A(x) + contamination
115 if (xp.min(exp) < 0) or (xp.min(x) < 0):
116 res = xp.finfo(xp.float64).max
117 else:
118 res = (xp.sum(exp - y * xp.log(exp))) + 0.5 * beta * xp.sum((x - x_prior) ** 2)
119 return res
DePierro update to minimize \(f(x)\)
We apply multiple DePierro updates
to calculate the minimizer of \(f(x)\) iteratively.
See [DP95] for more details.
139 cost = xp.zeros(num_iter, dtype=xp.float64, device=dev)
140
141 # "b" - modified sensitivity image
142 mod_adjoint_ones = (
143 op_A.adjoint(xp.ones(op_A.out_shape, dtype=xp.float64, device=dev)) - beta * x_prior
144 )
145
146 for i in range(num_iter):
147 # evaluate the forward model
148 exp = op_A(x) + contamination
149 ratio = y / exp
150 t = x * op_A.adjoint(ratio)
151 x = 2 * t / (xp.sqrt(mod_adjoint_ones**2 + 4 * beta * t) + mod_adjoint_ones)
152 cost[i] = cost_function(x)
153
154 print(f"Solution after {num_iter} DePierro iterations:")
155 print(x)
156 print(f"cost: {cost[-1]:.6e}")
Solution after 50 DePierro iterations:
[6.28680501 7.26148792 6.72092821 6.43913514]
cost: -3.321656e+02
159 if xp.__name__.endswith("numpy"):
160 from scipy.optimize import fmin_powell
161
162 x_ref = fmin_powell(cost_function, x, xtol=1e-6, ftol=1e-6)
163 rel_dist = float(xp.sum((x - x_ref) ** 2)) / float(xp.sum(x_ref**2))
164
165 print(f"\nReference solution using Powell optimizer:")
166 print(x_ref)
167 print(f"rel. distance to DePierro solution: {rel_dist:.2e}")
168 print(f"cost: {cost_function(x_ref):.6e}")
Optimization terminated successfully.
Current function value: -332.165557
Iterations: 1
Function evaluations: 113
Reference solution using Powell optimizer:
[6.28680485 7.26148793 6.72092821 6.43913496]
rel. distance to DePierro solution: 3.19e-16
cost: -3.321656e+02
171 fig, ax = plt.subplots(1, 1, tight_layout=True)
172 if xp.__name__.endswith("numpy"):
173 ax.axhline(cost_function(x_ref), color="k", linestyle="--")
174 ax.plot(parallelproj.to_numpy_array(cost))
175 ax.set_xlabel("iteration")
176 ax.set_ylabel("cost function")
177 ax.grid(ls=":")
178 fig.show()

Total running time of the script: (0 minutes 0.154 seconds)
Related examples
PDHG to optimize the Poisson logL and directional TV (structural prior)