"""Gradio app: detect cells in a fluorescence image. Upload an RGB image (blue = nuclei, red = cytoplasm), click Analyze, get one grayscale output per detected cell with cell + nucleus boundaries drawn in yellow. """ from __future__ import annotations import cv2 import gradio as gr import numpy as np from PIL import Image from quantification import analyze_image N_CELLS = 5 DILATION_RADIUS = 12 OUTLINE_COLOR_RGB = (255, 255, 0) OUTLINE_THICKNESS = 2 def analyze(image_path: str | None) -> list[np.ndarray]: if image_path is None: return [] arr = np.array(Image.open(image_path).convert("RGB")) if arr.dtype != np.uint8: arr = np.clip(arr, 0, 255).astype(np.uint8) red = arr[..., 0] gray_rgb = np.stack([red, red, red], axis=-1) cells = analyze_image(arr, n_cells=N_CELLS, dilation_radius=DILATION_RADIUS) outputs: list[np.ndarray] = [] for c in cells: canvas = gray_rgb.copy() for mask in (c.cell_mask, c.nucleus_mask): contours, _ = cv2.findContours( mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) cv2.drawContours(canvas, contours, -1, OUTLINE_COLOR_RGB, OUTLINE_THICKNESS) outputs.append(canvas) return outputs def build_demo() -> gr.Blocks: with gr.Blocks(title="Cell Boundary Detection") as demo: with gr.Row(): with gr.Column(): image_in = gr.Image(label="Upload image", type="filepath") run_btn = gr.Button("Analyze", variant="primary") gallery = gr.Gallery( label="Detected cells", columns=2, height=620, object_fit="contain", ) run_btn.click(analyze, inputs=image_in, outputs=gallery) return demo if __name__ == "__main__": build_demo().launch()