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