ai / app.py
stevafernandes's picture
Update app.py
d1ede70 verified
Raw
History Blame
5.12 kB
"""Gradio app: detect cells in a fluorescence image and return red-channel
grayscale images with cell + nucleus outlines drawn in yellow.
One output image is produced per detected cell, matching the documentation
style: grayscale background + two concentric yellow outlines, nothing else.
"""
from __future__ import annotations
import os
import cv2
import gradio as gr
import numpy as np
from PIL import Image
from quantification import analyze_image
DEFAULT_N_CELLS = 5
DEFAULT_DILATION_RADIUS = 12
OUTLINE_COLOR_BGR_AS_RGB = (255, 255, 0) # yellow in RGB
OUTLINE_THICKNESS = 2
EXAMPLES_DIR = os.path.join(os.path.dirname(__file__), "examples")
DEFAULT_EXAMPLE = os.path.join(EXAMPLES_DIR, "Picture1.jpg")
def _ensure_rgb(arr: np.ndarray) -> np.ndarray:
if arr.ndim == 2:
arr = np.stack([arr, arr, arr], axis=-1)
if arr.shape[2] == 4:
arr = arr[..., :3]
if arr.dtype != np.uint8:
arr = np.clip(arr, 0, 255).astype(np.uint8)
return arr
def _draw_cell_outline(
gray_rgb: np.ndarray,
cell_mask: np.ndarray,
nucleus_mask: np.ndarray,
) -> np.ndarray:
"""Draw the outer (cell) and inner (nucleus) outlines on a copy of `gray_rgb`."""
out = gray_rgb.copy()
for mask in (cell_mask, nucleus_mask):
contours, _ = cv2.findContours(
mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
cv2.drawContours(
out, contours, -1, OUTLINE_COLOR_BGR_AS_RGB, OUTLINE_THICKNESS
)
return out
def process_image(image_path: str | None, n_cells: int, dilation_radius: int):
"""Return a list of one annotated image per detected cell."""
if image_path is None:
return []
image_pil = Image.open(image_path).convert("RGB")
image_rgb = _ensure_rgb(np.array(image_pil))
# Background for outputs: the red channel rendered as a grayscale RGB.
red = image_rgb[..., 0]
gray_rgb = np.stack([red, red, red], axis=-1)
cells = analyze_image(
image_rgb,
n_cells=int(n_cells),
dilation_radius=int(dilation_radius),
)
return [
_draw_cell_outline(gray_rgb, c.cell_mask, c.nucleus_mask) for c in cells
]
def build_demo() -> gr.Blocks:
description = (
"Upload a fluorescence image (RGB: blue = nuclei, red = cytoplasm). "
"The app detects representative cells and returns the red channel as "
"grayscale with the cell + nucleus boundaries drawn in yellow — one "
"output image per cell."
)
with gr.Blocks(title="Cell Boundary Detection") as demo:
gr.Markdown("# Cell Boundary Detection")
gr.Markdown(description)
with gr.Row():
with gr.Column(scale=1):
image_in = gr.Image(
label="Input image",
type="filepath",
value=DEFAULT_EXAMPLE if os.path.exists(DEFAULT_EXAMPLE) else None,
)
n_cells_slider = gr.Slider(
minimum=1,
maximum=10,
value=DEFAULT_N_CELLS,
step=1,
label="Number of cells",
)
dilation_slider = gr.Slider(
minimum=4,
maximum=30,
value=DEFAULT_DILATION_RADIUS,
step=1,
label="Cytoplasm ring thickness (pixels)",
)
run_btn = gr.Button("Detect cells", variant="primary")
with gr.Column(scale=2):
gallery = gr.Gallery(
label="Detected cells (one per image)",
columns=2,
height=620,
show_label=True,
object_fit="contain",
)
run_btn.click(
fn=process_image,
inputs=[image_in, n_cells_slider, dilation_slider],
outputs=[gallery],
)
# Example images (other defaults from prior dataset).
example_files = []
if os.path.isdir(EXAMPLES_DIR):
example_files = sorted(
os.path.join(EXAMPLES_DIR, f)
for f in os.listdir(EXAMPLES_DIR)
if f.lower().endswith((".jpg", ".jpeg", ".png", ".tif", ".tiff"))
)
if example_files:
gr.Examples(
examples=[[p, DEFAULT_N_CELLS, DEFAULT_DILATION_RADIUS]
for p in example_files],
inputs=[image_in, n_cells_slider, dilation_slider],
outputs=[gallery],
fn=process_image,
cache_examples=False,
label="Example images",
)
# Preload outputs for the default image on app start.
if os.path.exists(DEFAULT_EXAMPLE):
demo.load(
fn=process_image,
inputs=[image_in, n_cells_slider, dilation_slider],
outputs=[gallery],
)
return demo
if __name__ == "__main__":
demo = build_demo()
demo.launch()