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() |