File size: 5,823 Bytes
3f658fa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | 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()
|