Skip to content

Commit 91d988a

Browse files
authored
Merge pull request #532 from fedor-goncharov/tutorial-ctscan-correction
remark on classical sinograms added + jacobian correction; added two …
2 parents 2a69b82 + 41dc437 commit 91d988a

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,4 @@ A list of video tutorials to learn more about PyLops:
148148
* Aniket Singh Rawat, dikwickley
149149
* Rohan Babbar, rohanbabbar04
150150
* Wei Zhang, ZhangWeiGeo
151+
* Fedor Goncharov, fedor-goncharov

docs/source/credits.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ Contributors
1919
* `Juan Daniel Romero <https://github.com/jdromerom>`_, jdromerom
2020
* `Aniket Singh Rawat <https://github.com/dikwickley>`_, dikwickley
2121
* `Rohan Babbar <https://github.com/rohanbabbar04>`_, rohanbabbar04
22-
* `Wei Zhang <https://github.com/ZhangWeiGeo>`_, ZhangWeiGeo
22+
* `Wei Zhang <https://github.com/ZhangWeiGeo>`_, ZhangWeiGeo
23+
* `Fedor Goncharov <https://github.com/fedor-goncharov>`_, fedor-goncharov

tutorials/ctscan.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@
2828
# such a type:
2929
#
3030
# .. math::
31-
# t(r,\theta; x) = \tan(90°-\theta)x + \frac{r}{\sin(\theta)}
31+
# t\sin(\theta) + x\cos(\theta) = r,
3232
#
3333
# where :math:`\theta` is the angle between the x-axis (:math:`x`) and
3434
# the perpendicular to the summation line and :math:`r` is the distance
35-
# from the origin of the summation line.
35+
# from the origin of the summation line. Radon transform in CT
36+
# corresponds to the integral of the input image along the straight line above.
37+
# To implement the integration in PyLops we simply need to express
38+
# :math:`t(r,\theta;x)` which is given by:
39+
#
40+
# .. math::
41+
# t(r,\theta; x) = \tan\left(\frac{\pi}{2}-\theta\right)x + \frac{r}{\sin(\theta)}.
3642

3743

3844
@jit(nopython=True)
@@ -44,6 +50,13 @@ def radoncurve(x, r, theta):
4450
)
4551

4652

53+
###############################################################################
54+
# Note that in the above implementation we added centering :math:`t \mapsto t - n_y/2` and
55+
# :math:`r \mapsto r - n_y/2` so that the origin of the integration lines is exactly in the
56+
# center of the image (centering for :math:`x` is not needed because we will use
57+
# ``centeredh=True`` in the constructor of ``Radon2D``).
58+
59+
4760
x = np.load("../testdata/optimization/shepp_logan_phantom.npy").T
4861
x = x / x.max()
4962
nx, ny = x.shape
@@ -81,14 +94,36 @@ def radoncurve(x, r, theta):
8194
axs[1].set_title("Data")
8295
axs[1].axis("tight")
8396
axs[2].imshow(xrec.T, cmap="gray")
84-
axs[2].set_title("Adjoint model")
97+
axs[2].set_title("Adjoint model in PyLops")
8598
axs[2].axis("tight")
8699
fig.tight_layout()
87100

101+
###############################################################################
102+
# Note that our raw data ``y`` *does not represent exactly* classical sinograms
103+
# in medical imaging. Integration along curves in the adjoint form of
104+
# :func:`pylops.signalprocessing.Radon2D` is performed with respect to
105+
# :math:`dx`, whereas canonically it is assumed to be with respect to the natural
106+
# parametrization :math:`dl = \sqrt{(dx)^2 + (dt)^2}`. To retrieve back the
107+
# classical sinogram we have to divide data by the jacobian
108+
# :math:`j(x,l) = \left\vert dx/dl \right\vert = |\sin(\theta)|`.
109+
110+
sinogram = np.divide(
111+
y.T, np.abs(np.sin(theta) + 1e-15)
112+
) # small shift to avoid zero-division
113+
fig, axs = plt.subplots(1, 2, figsize=(10, 4))
114+
axs[0].imshow(y.T, cmap="gray")
115+
axs[0].set_title("Data")
116+
axs[0].axis("tight")
117+
axs[1].imshow(sinogram, cmap="gray")
118+
axs[1].set_title("Sinogram in medical imaging")
119+
axs[1].axis("tight")
120+
fig.tight_layout()
88121

89122
###############################################################################
90-
# Finally we take advantage of our different solvers and try to invert the
91-
# modelling operator both in a least-squares sense and using TV-reg.
123+
# From now on, we will not pursue further working with the "true sinogram", instead
124+
# we will reconstruct the original phantom directly from ``y``. For this we take advantage
125+
# of our different solvers and try to invert the modelling operator both in a
126+
# least-squares sense and using TV-reg.
92127
Dop = [
93128
pylops.FirstDerivative(
94129
(nx, ny), axis=0, edge=True, kind="backward", dtype=np.float64

0 commit comments

Comments
 (0)