fr1ll commited on
Commit
3a2ac5e
·
verified ·
1 Parent(s): 897699e

Can 3o get it first try

Browse files
Files changed (1) hide show
  1. app.py +121 -0
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()