Spaces:
Sleeping
Sleeping
import gradio as gr | |
import numpy as np | |
from pylops.signalprocessing import Radon2D | |
from typing import Literal | |
def compute_radon_fn(image, | |
kind: Literal["linear", "hyperbolic", "parabolic"] = "linear"): | |
# Convert image to grayscale if needed. | |
if image.ndim == 3: | |
image_gray = np.mean(image, axis=2) | |
else: | |
image_gray = image | |
nt, nh = image_gray.shape | |
# Define axes centered around zero. | |
taxis = np.linspace(-nt/2, nt/2, nt) # time axis (rows) | |
haxis = np.linspace(-nh/2, nh/2, nh) # spatial axis (columns) | |
# Set detector axis (pxaxis) to cover the full image diagonal. | |
# npx = int(np.ceil(np.sqrt(nt**2 + nh**2))) | |
npx = nh # why? | |
pxaxis = np.linspace(-npx/2, npx/2, npx) | |
print(f"Shapes:\n taxis:{taxis.shape}, haxis:{haxis.shape}, pxaxis:{pxaxis.shape}") | |
# Create the Radon2D operator with engine 'numba' and centeredh=True. | |
R = Radon2D(taxis, haxis, pxaxis, | |
kind=kind, centeredh=True, interp=True, | |
onthefly=False, engine='numpy', dtype='float64', name='R') | |
# Compute the forward radon transform. | |
radon_data_flat = R.dot(image_gray.flatten()) | |
# Deduce the number of projections from the operator shape. | |
nproj = R.shape[0] // npx | |
# Reshape to (nproj, L) so each row corresponds to one projection. | |
radon_data = radon_data_flat.reshape((nproj, npx)) | |
# Transpose for display: p (detector coordinate) on x-axis, τ on y-axis. | |
radon_display = radon_data.T | |
# Normalize for display. | |
radon_display = (radon_display - radon_display.min()) / (radon_display.max() - radon_display.min() + 1e-8) | |
# Save the state including the radon data and operator for inverse computation. | |
state = { | |
"radon_data": radon_data, # shape: (nproj, L) | |
"image_shape": image_gray.shape, | |
"R": R, # the Radon2D operator | |
"L": npx # detector length | |
} | |
return radon_display, state | |
def apply_mask_and_inverse_fn(state, mask_image): | |
if mask_image is None: | |
return None | |
# Ensure mask is single-channel. | |
if mask_image.ndim == 3: | |
mask_image = mask_image[..., 0] | |
radon_data = state["radon_data"] | |
image_shape = tuple(state["image_shape"]) | |
L = state["L"] | |
# The displayed radon image was transposed, so transpose mask back. | |
mask = mask_image.T | |
# Create binary mask: painted pixels (value > 0.5) become 1. | |
mask_binary = (mask > 0.5).astype(float) | |
# Apply the mask: zero-out masked pixels in the radon data. | |
radon_masked = radon_data * (1 - mask_binary) | |
# Reconstruct the image using the adjoint (transpose) of the operator. | |
R = state["R"] | |
rec_flat = R.T.dot(radon_masked.flatten()) | |
rec = rec_flat.reshape(image_shape) | |
# Normalize reconstruction for display. | |
rec_norm = (rec - rec.min()) / (rec.max() - rec.min() + 1e-8) | |
return rec_norm | |
with gr.Blocks() as demo: | |
gr.Markdown("## Radon Transform with Interactive Masking (Using PyLops Radon2D)") | |
with gr.Row(): | |
image_input = gr.Image(label="Input Image", type="numpy") | |
compute_button = gr.Button("Compute Radon") | |
radon_output = gr.Image(label="Radon Transform Image", interactive=False, type="numpy") | |
# Use the updated ImageEditor component for interactive masking. | |
radon_drawing = gr.ImageEditor(label="Paint on Radon (mask out pixels)", type="numpy") | |
inverse_output = gr.Image(label="Reconstructed Image", interactive=False, type="numpy") | |
# State to hold radon data and operator. | |
state = gr.State() | |
# When "Compute Radon" is clicked, compute the radon transform. | |
compute_button.click( | |
fn=compute_radon_fn, | |
inputs=[image_input], | |
outputs=[radon_output, state] | |
) | |
# When the user edits (paints) the radon image, apply the mask and compute the inverse. | |
radon_drawing.change( | |
fn=apply_mask_and_inverse_fn, | |
inputs=[state, radon_drawing], | |
outputs=[inverse_output] | |
) | |
gr.Markdown( | |
"**Instructions:** Upload an image and click 'Compute Radon' to compute the radon transform using PyLops’ Radon2D (with engine 'numba' and centeredh=True). Then use the paintbrush tool to mask parts of the radon image and see the resulting reconstruction." | |
) | |
demo.launch() |