import gradio as gr import numpy as np import pandas as pd from csbdeep.utils import normalize from skimage.color import rgb2gray from skimage.measure import regionprops from skimage.morphology import binary_closing from skimage.util import img_as_ubyte from skimage.measure import shannon_entropy from stardist.models import StarDist2D from stardist.plot import render_label MODEL_NAMES = [ "2D_versatile_fluo", "2D_versatile_he", "2D_paper_dsb2018", ] _MODEL_CACHE = {} def get_model(name: str) -> StarDist2D: if name not in _MODEL_CACHE: _MODEL_CACHE[name] = StarDist2D.from_pretrained(name) return _MODEL_CACHE[name] def to_gray(image: np.ndarray) -> np.ndarray: if image.ndim == 2: return image return rgb2gray(image) def box_counting_fd(mask: np.ndarray) -> float: if mask.sum() == 0: return 0.0 sizes = np.array([2, 4, 8, 16, 32, 64]) sizes = sizes[sizes <= min(mask.shape)] counts = [] for size in sizes: shape = ( int(np.ceil(mask.shape[0] / size)), int(np.ceil(mask.shape[1] / size)), ) pad_h = shape[0] * size - mask.shape[0] pad_w = shape[1] * size - mask.shape[1] padded = np.pad(mask, ((0, pad_h), (0, pad_w)), mode="constant") blocks = padded.reshape(shape[0], size, shape[1], size) blocks = blocks.any(axis=(1, 3)) counts.append(np.count_nonzero(blocks)) counts = np.array(counts) sizes = sizes[counts > 0] counts = counts[counts > 0] if len(counts) < 2: return 0.0 coeffs = np.polyfit(np.log(1 / sizes), np.log(counts), 1) return float(coeffs[0]) def compute_metrics( labels: np.ndarray, intensity_image: np.ndarray, width_units: float ): props = regionprops(labels, intensity_image=intensity_image) image_width = labels.shape[1] pixel_size = (width_units / image_width) if image_width > 0 else 0.0 rows = [] for region in props: area_px = float(region.area) perimeter_px = float(region.perimeter) major_px = float(region.major_axis_length) if region.major_axis_length else 0.0 minor_px = float(region.minor_axis_length) if region.minor_axis_length else 0.0 area = area_px * (pixel_size**2) perimeter = perimeter_px * pixel_size major = major_px * pixel_size minor = minor_px * pixel_size aspect_ratio = major / minor if minor > 0 else 0.0 circularity = (4 * np.pi * area / (perimeter**2)) if perimeter > 0 else 0.0 roundness = (4 * area / (np.pi * major**2)) if major > 0 else 0.0 region_mask = labels == region.label region_mask = binary_closing(region_mask) entropy_val = float( shannon_entropy(region.intensity_image[region.image], base=2) ) fractal_dim = box_counting_fd(region_mask) integrated_density = float(region.intensity_image.sum()) * (pixel_size**2) ecc_rel = float(region.eccentricity * major) rows.append( { "Label": int(region.label), "Area": area, "Perimeter": perimeter, "Aspect ratio": aspect_ratio, "Circularity": circularity, "Roundness": roundness, "Entropy": entropy_val, "Fractal dimension": fractal_dim, "Integrated density": integrated_density, "Eccentricity (rel width)": ecc_rel, } ) metrics_df = pd.DataFrame(rows) avg_df = pd.DataFrame() if not metrics_df.empty: numeric_cols = metrics_df.columns.drop("Label") avg_row = {"Metric": "Average"} avg_row.update(metrics_df[numeric_cols].mean().to_dict()) avg_df = pd.DataFrame([avg_row]) return metrics_df, avg_df def run_inference(image: np.ndarray, model_name: str, width_units: float): if image is None: return None, None, None if width_units <= 0: width_units = 1.0 model = get_model(model_name) image_input = image.copy() if model_name == "2D_versatile_fluo": image_input = to_gray(image_input) image_norm = normalize(image_input, 1, 99.8, axis=(0, 1)) labels, _ = model.predict_instances(image_norm) overlay = render_label(labels, img=image) if np.issubdtype(overlay.dtype, np.floating): overlay = np.clip(overlay, 0, 1) overlay = img_as_ubyte(overlay) intensity_image = to_gray(image) metrics_df, avg_df = compute_metrics(labels, intensity_image, width_units) return overlay, metrics_df, avg_df with gr.Blocks(title="StarDist 2D Segmentation - HF app by Ram Sevuggan") as demo: gr.Markdown("# StarDist 2D Segmentation - HF app by Ram Sevuggan") with gr.Row(): with gr.Column(): input_image = gr.Image(label="Input image", type="numpy") model_dropdown = gr.Dropdown( choices=MODEL_NAMES, value="2D_versatile_fluo", label="Model", ) width_units = gr.Number( value=1.0, minimum=1e-6, label="Image width (units)", info="Used for eccentricity relative to image width", ) run_button = gr.Button("Run") with gr.Column(): output_image = gr.Image(label="Overlay", type="numpy") metrics_table = gr.Dataframe( label="Object metrics", interactive=False, ) avg_table = gr.Dataframe( label="Average metrics", interactive=False, ) run_button.click( fn=run_inference, inputs=[input_image, model_dropdown, width_units], outputs=[output_image, metrics_table, avg_table], ) if __name__ == "__main__": demo.launch()