Spaces:
Sleeping
Sleeping
Can 3o get it first try
Browse files
app.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import numpy as np
|
3 |
+
from pylops.signalprocessing import Radon
|
4 |
+
|
5 |
+
def compute_radon_fn(image, num_angles, angle_min, angle_max):
|
6 |
+
"""
|
7 |
+
Given an input image and radon parameters, compute the radon transform.
|
8 |
+
The output sinogram is reshaped and transposed so that the horizontal axis corresponds to p values,
|
9 |
+
and the vertical axis corresponds to the tau (angle index).
|
10 |
+
A state dictionary is returned with the original sinogram, image shape, and angles.
|
11 |
+
"""
|
12 |
+
# Convert image to grayscale if it has multiple channels.
|
13 |
+
if image.ndim == 3:
|
14 |
+
image_gray = np.mean(image, axis=2)
|
15 |
+
else:
|
16 |
+
image_gray = image
|
17 |
+
|
18 |
+
# Create an array of angles (in degrees).
|
19 |
+
angles = np.linspace(angle_min, angle_max, int(num_angles), endpoint=False)
|
20 |
+
|
21 |
+
# Create the radon operator for the given image shape and angles.
|
22 |
+
R = Radon(image_gray.shape, angles=angles)
|
23 |
+
# Compute the radon transform (operator acts on the flattened image).
|
24 |
+
sinogram_flat = R.dot(image_gray.flatten())
|
25 |
+
|
26 |
+
# The radon operator R has shape (num_angles * L, N) where N is the image size.
|
27 |
+
# We deduce L (the length of each projection) from R.shape.
|
28 |
+
L = R.shape[0] // len(angles)
|
29 |
+
# Reshape to (num_angles, L); note that each row corresponds to one angle.
|
30 |
+
sinogram = sinogram_flat.reshape((len(angles), L))
|
31 |
+
|
32 |
+
# For display, we want p on the x-axis and tau (angle index) on the y-axis.
|
33 |
+
# Transpose the sinogram to get shape (L, num_angles).
|
34 |
+
sinogram_display = sinogram.T
|
35 |
+
# Normalize the display image to [0,1].
|
36 |
+
sinogram_display = (sinogram_display - sinogram_display.min()) / (sinogram_display.max() - sinogram_display.min() + 1e-8)
|
37 |
+
|
38 |
+
# Pack the parameters and the original sinogram into a state dict.
|
39 |
+
state = {
|
40 |
+
"sinogram": sinogram, # shape: (num_angles, L)
|
41 |
+
"image_shape": image_gray.shape, # original image dimensions
|
42 |
+
"angles": angles.tolist() # store as a list for JSON-serialization
|
43 |
+
}
|
44 |
+
|
45 |
+
return sinogram_display, state
|
46 |
+
|
47 |
+
def apply_mask_and_inverse_fn(state, mask_image):
|
48 |
+
"""
|
49 |
+
Given the state (which includes the original sinogram, image shape, and angles)
|
50 |
+
and a drawing (mask) provided on the radon image, this function applies the mask
|
51 |
+
(setting painted sinogram pixels to zero) and then computes an inverse transform
|
52 |
+
via the adjoint of the Radon operator.
|
53 |
+
"""
|
54 |
+
if mask_image is None:
|
55 |
+
return None
|
56 |
+
|
57 |
+
# Ensure the mask is single-channel.
|
58 |
+
if mask_image.ndim == 3:
|
59 |
+
mask_image = mask_image[..., 0]
|
60 |
+
|
61 |
+
# Retrieve stored sinogram and parameters.
|
62 |
+
sinogram = state["sinogram"] # shape: (num_angles, L)
|
63 |
+
image_shape = tuple(state["image_shape"])
|
64 |
+
angles = np.array(state["angles"])
|
65 |
+
|
66 |
+
# The displayed sinogram was transposed (shape (L, num_angles)); we transpose the mask
|
67 |
+
# to align with the stored sinogram (shape (num_angles, L)).
|
68 |
+
mask = mask_image.T
|
69 |
+
# Create a binary mask; here we consider pixels with value > 0.5 as “painted.”
|
70 |
+
mask_binary = (mask > 0.5).astype(float)
|
71 |
+
|
72 |
+
# Apply the mask to the sinogram (zeroing out painted pixels).
|
73 |
+
sinogram_masked = sinogram * (1 - mask_binary)
|
74 |
+
|
75 |
+
# Reconstruct the image using the adjoint of the radon operator.
|
76 |
+
R = Radon(image_shape, angles=angles)
|
77 |
+
# The adjoint (transpose) of the radon operator is used as an approximation for the inverse.
|
78 |
+
rec_flat = R.T.dot(sinogram_masked.flatten())
|
79 |
+
rec = rec_flat.reshape(image_shape)
|
80 |
+
|
81 |
+
# Normalize the reconstructed image to [0,1] for display.
|
82 |
+
rec_norm = (rec - rec.min()) / (rec.max() - rec.min() + 1e-8)
|
83 |
+
return rec_norm
|
84 |
+
|
85 |
+
with gr.Blocks() as demo:
|
86 |
+
gr.Markdown("## Radon Transform with Interactive Masking")
|
87 |
+
|
88 |
+
with gr.Row():
|
89 |
+
image_input = gr.Image(label="Input Image", type="numpy")
|
90 |
+
with gr.Column():
|
91 |
+
num_angles_slider = gr.Slider(10, 360, step=1, value=180, label="Number of Angles")
|
92 |
+
angle_min_slider = gr.Slider(0, 360, step=1, value=0, label="Angle Min (degrees)")
|
93 |
+
angle_max_slider = gr.Slider(0, 360, step=1, value=180, label="Angle Max (degrees)")
|
94 |
+
|
95 |
+
compute_button = gr.Button("Compute Radon")
|
96 |
+
radon_output = gr.Image(label="Radon Transform", interactive=False, type="numpy")
|
97 |
+
radon_drawing = gr.Image(label="Paint on Radon (mask out pixels)", type="numpy", tool="sketch")
|
98 |
+
inverse_output = gr.Image(label="Reconstructed Image", interactive=False, type="numpy")
|
99 |
+
|
100 |
+
# State to store parameters and the computed sinogram.
|
101 |
+
state = gr.State()
|
102 |
+
|
103 |
+
# When the user clicks the button, compute the radon transform.
|
104 |
+
compute_button.click(
|
105 |
+
fn=compute_radon_fn,
|
106 |
+
inputs=[image_input, num_angles_slider, angle_min_slider, angle_max_slider],
|
107 |
+
outputs=[radon_output, state]
|
108 |
+
)
|
109 |
+
|
110 |
+
# When the user paints on the radon image, apply the mask and compute the inverse.
|
111 |
+
radon_drawing.change(
|
112 |
+
fn=apply_mask_and_inverse_fn,
|
113 |
+
inputs=[state, radon_drawing],
|
114 |
+
outputs=[inverse_output]
|
115 |
+
)
|
116 |
+
|
117 |
+
gr.Markdown(
|
118 |
+
"**Instructions:** Upload an image and adjust the radon parameters. Click 'Compute Radon' to see the sinogram (displayed with p values on the x-axis and τ on the y-axis). Then use the paintbrush tool to mask out regions of the sinogram and see how the inverse transform (reconstruction) changes."
|
119 |
+
)
|
120 |
+
|
121 |
+
demo.launch()
|