Skip to content

Commit 281e5af

Browse files
authored
Add support for primal domain regularization and FISTA algorithm (#26)
Signed-off-by: Nicola VIGANÒ <nicola.vigano@cea.fr>
1 parent 13cb9e9 commit 281e5af

9 files changed

Lines changed: 1243 additions & 164 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This package provides the following functionality:
1919
* Various data fitting terms, including **Gaussian and Poisson noise** modelling.
2020
* Various optional regularization terms, including: **TV-min**, l1-min, laplacian, and **wavelet** l1-min.
2121
* Multi-channel (collaborative) regularization terms, like: **TNV** (Total Nuclear Variation).
22+
- Fast Iterative Shrinkage-Thresholding Algorithm (FISTA).
2223
- Filtered Back-Projection (**FBP**), and its data-dependent filter learning variant
2324
(**[PyMR-FBP](https://github.com/dmpelt/pymrfbp)**).
2425
* Two projector backends, based on: [astra-toolbox](https://github.com/astra-toolbox/astra-toolbox) and

doc_sources/tutorial.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,15 @@ filter can be selected, by passing the `fbp_filter` parameter at initialization:
124124
solver_fbp = cct.solvers.FBP(fbp_filter="shepp-logan")
125125
```
126126

127-
#### SIRT, PDHG, and MLEM
127+
#### SIRT, PDHG, MLEM, and FISTA
128128

129-
The SIRT and PDHG algorithms, are algebraic (iterative) methods. They both
129+
The SIRT, PDHG and FISTA algorithms, are algebraic (iterative) methods. They all
130130
support regularization, and box constraints on the solution. The PDHG also
131-
supports various data fidelity terms.
132-
The MLEM algorithm is also an iterative algorithm to find the maximum likelihood estimation of the reconstructed signal. The MLEM does not currently support any regularization.
131+
supports various data fidelity terms. FISTA only supports regularizations that
132+
form a tight Parseval frame (like certain wavelet transforms).
133+
The MLEM algorithm is also an iterative algorithm to find the maximum likelihood
134+
estimation of the reconstructed signal. The MLEM does not currently support any
135+
regularization.
133136

134137
The interface of the iterative methods is the same as for the FBP, with the only
135138
difference of requiring an iterations count:
@@ -146,7 +149,7 @@ with cct.projectors.ProjectorUncorrected(vol_shape_xy, angles_rad) as p:
146149
vol, _ = solver_sirt(p, sino, iterations=100)
147150
```
148151

149-
It is possible to specify an intial solution or box limits on the solutions like
152+
It is possible to specify an initial solution or box limits on the solutions like
150153
the following:
151154
```python
152155
x0 = np.ones(vol_shape_xy) # Initial solution
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""
2+
This example compares the fista solver against SIRT and weighted least-squares
3+
reconstructions (implemented with the PDHG algorithm) of the Shepp-Logan phantom.
4+
5+
@author: Jérome Lesaint, ESRF - The European Synchrotron, Grenoble, France
6+
"""
7+
8+
from collections.abc import Sequence
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
import corrct as cct
13+
14+
try:
15+
import phantom
16+
except ImportError:
17+
cct.testing.download_phantom()
18+
import phantom
19+
20+
21+
def cm2inch(x: Sequence[float]) -> tuple[float, float]:
22+
"""Convert cm to inch.
23+
24+
Parameters
25+
----------
26+
x : ArrayLike
27+
Sizes in cm.
28+
29+
Returns
30+
-------
31+
tuple[float, float]
32+
Sizes in inch.
33+
"""
34+
return tuple(np.array(x) / 2.54)
35+
36+
37+
vol_shape = [256, 256, 3]
38+
data_type = np.float32
39+
40+
ph_or = np.squeeze(phantom.modified_shepp_logan(vol_shape).astype(data_type))
41+
ph_or = ph_or[:, :, 1]
42+
43+
ph, vol_att_in, vol_att_out = cct.testing.phantom_assign_concentration(ph_or)
44+
# Create sino with no background noise.
45+
sino, angles, expected_ph, background_avg = cct.testing.create_sino(
46+
ph, 30, psf=None, add_poisson=True, dwell_time_s=1e-1, background_avg=1e-5, background_std=1e-6, dtype=np.float32
47+
)
48+
49+
num_iterations = 100
50+
lower_limit = 0
51+
vol_mask = cct.processing.circular_mask(ph_or.shape)
52+
53+
sino_variance = cct.processing.compute_variance_poisson(sino)
54+
sino_weights = cct.processing.compute_variance_weight(sino_variance, normalized=True)
55+
56+
data_term_ls = cct.data_terms.DataFidelity_l2(background=background_avg)
57+
data_term_lsw = cct.data_terms.DataFidelity_wl2(sino_weights, background=background_avg)
58+
data_term_kl = cct.data_terms.DataFidelity_KL(background=background_avg)
59+
60+
with cct.projectors.ProjectorUncorrected(ph.shape, angles) as A:
61+
reg = cct.regularizers.Regularizer_l1dwl(1e-1, "Haar", 3)
62+
solver_pdhg = cct.solvers.PDHG(verbose=True, data_term=data_term_kl, regularizer=reg)
63+
rec_pdhg, _ = solver_pdhg(A, sino, num_iterations, x_mask=vol_mask)
64+
65+
solver_sirt = cct.solvers.SIRT(verbose=True, data_term=data_term_lsw)
66+
rec_sirt, _ = solver_sirt(A, sino, num_iterations, x_mask=vol_mask)
67+
68+
reg = cct.regularizers.Regularizer_l1dwl(1e-2, "Haar", 3)
69+
solver_fista = cct.solvers.FISTA(verbose=True, data_term=data_term_lsw, regularizer=reg)
70+
rec_fista, _ = solver_fista(A, sino, num_iterations, x_mask=vol_mask)
71+
72+
73+
vminmax = dict(vmin=expected_ph.min(), vmax=expected_ph.max())
74+
75+
# Reconstructions
76+
fig = plt.figure(figsize=cm2inch([36, 24]))
77+
gs = fig.add_gridspec(8, 3)
78+
ax_ph = fig.add_subplot(gs[:4, 0])
79+
im_ph = ax_ph.imshow(expected_ph, **vminmax)
80+
ax_ph.set_title("Phantom")
81+
fig.colorbar(im_ph, ax=ax_ph)
82+
83+
ax_sino_clean = fig.add_subplot(gs[4, 0])
84+
with cct.projectors.ProjectorUncorrected(ph_or.shape, angles) as p:
85+
sino_clean = p.fp(expected_ph)
86+
im_sino_clean = ax_sino_clean.imshow(sino_clean)
87+
ax_sino_clean.set_title("Clean sinogram")
88+
89+
ax_sino_noise = fig.add_subplot(gs[5, 0])
90+
im_sino_noise = ax_sino_noise.imshow(sino - background_avg)
91+
ax_sino_noise.set_title("Noisy sinogram")
92+
93+
ax_sino_lines = fig.add_subplot(gs[6:, 0])
94+
im_sino_lines = ax_sino_lines.plot(sino[9, :] - background_avg, label="Noisy")
95+
im_sino_lines = ax_sino_lines.plot(sino_clean[9, :], label="Clean")
96+
ax_sino_lines.set_title("Sinograms - angle: 10")
97+
ax_sino_lines.legend()
98+
ax_sino_lines.grid()
99+
100+
ax_wls_l = fig.add_subplot(gs[:4, 1], sharex=ax_ph, sharey=ax_ph)
101+
im_wls_l = ax_wls_l.imshow(np.squeeze(rec_pdhg), **vminmax)
102+
ax_wls_l.set_title(solver_pdhg.info().upper())
103+
fig.colorbar(im_wls_l, ax=ax_wls_l)
104+
105+
ax_sirt = fig.add_subplot(gs[4:, 1], sharex=ax_ph, sharey=ax_ph)
106+
im_sirt = ax_sirt.imshow(np.squeeze(rec_sirt), **vminmax)
107+
ax_sirt.set_title(solver_sirt.info().upper())
108+
fig.colorbar(im_sirt, ax=ax_sirt)
109+
110+
ax_fista = fig.add_subplot(gs[:4, 2], sharex=ax_ph, sharey=ax_ph)
111+
im_fista = ax_fista.imshow(np.squeeze(rec_fista), **vminmax)
112+
ax_fista.set_title(solver_fista.info().upper())
113+
fig.colorbar(im_fista, ax=ax_fista)
114+
115+
axs = fig.add_subplot(gs[4:, 2])
116+
axs.plot(np.squeeze(expected_ph[..., 172]), label="Phantom")
117+
axs.plot(np.squeeze(rec_pdhg[..., 172]), label=solver_pdhg.info().upper())
118+
axs.plot(np.squeeze(rec_sirt[..., 172]), label=solver_sirt.info().upper())
119+
axs.plot(np.squeeze(rec_fista[..., 172]), label=solver_fista.info().upper())
120+
axs.grid()
121+
axs.legend()
122+
fig.tight_layout()
123+
124+
# Comparing FRCs for each reconstruction
125+
labels = [solver_pdhg.info().upper(), solver_sirt.info().upper(), solver_fista.info().upper()]
126+
vols = [rec_pdhg, rec_sirt, rec_fista]
127+
cct.processing.post.plot_frcs([(expected_ph, rec) for rec in vols], labels=labels, snrt=0.4142)
128+
129+
plt.show(block=False)

0 commit comments

Comments
 (0)