TheSeriousProgrammer commited on
Commit
3f658fa
·
1 Parent(s): 2a7eae8

added files

Browse files
Files changed (2) hide show
  1. app.py +168 -0
  2. requirements.txt +91 -0
app.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import pandas as pd
4
+ from csbdeep.utils import normalize
5
+ from skimage.color import rgb2gray
6
+ from skimage.measure import regionprops
7
+ from skimage.morphology import binary_closing
8
+ from skimage.util import img_as_ubyte
9
+ from skimage.measure import shannon_entropy
10
+ from stardist.models import StarDist2D
11
+ from stardist.plot import render_label
12
+
13
+
14
+ MODEL_NAMES = [
15
+ "2D_versatile_fluo",
16
+ "2D_versatile_he",
17
+ "2D_paper_dsb2018",
18
+ ]
19
+
20
+
21
+ _MODEL_CACHE = {}
22
+
23
+
24
+ def get_model(name: str) -> StarDist2D:
25
+ if name not in _MODEL_CACHE:
26
+ _MODEL_CACHE[name] = StarDist2D.from_pretrained(name)
27
+ return _MODEL_CACHE[name]
28
+
29
+
30
+ def to_gray(image: np.ndarray) -> np.ndarray:
31
+ if image.ndim == 2:
32
+ return image
33
+ return rgb2gray(image)
34
+
35
+
36
+ def box_counting_fd(mask: np.ndarray) -> float:
37
+ if mask.sum() == 0:
38
+ return 0.0
39
+ sizes = np.array([2, 4, 8, 16, 32, 64])
40
+ sizes = sizes[sizes <= min(mask.shape)]
41
+ counts = []
42
+ for size in sizes:
43
+ shape = (
44
+ int(np.ceil(mask.shape[0] / size)),
45
+ int(np.ceil(mask.shape[1] / size)),
46
+ )
47
+ pad_h = shape[0] * size - mask.shape[0]
48
+ pad_w = shape[1] * size - mask.shape[1]
49
+ padded = np.pad(mask, ((0, pad_h), (0, pad_w)), mode="constant")
50
+ blocks = padded.reshape(shape[0], size, shape[1], size)
51
+ blocks = blocks.any(axis=(1, 3))
52
+ counts.append(np.count_nonzero(blocks))
53
+ counts = np.array(counts)
54
+ sizes = sizes[counts > 0]
55
+ counts = counts[counts > 0]
56
+ if len(counts) < 2:
57
+ return 0.0
58
+ coeffs = np.polyfit(np.log(1 / sizes), np.log(counts), 1)
59
+ return float(coeffs[0])
60
+
61
+
62
+ def compute_metrics(
63
+ labels: np.ndarray, intensity_image: np.ndarray, width_units: float
64
+ ):
65
+ props = regionprops(labels, intensity_image=intensity_image)
66
+ image_width = labels.shape[1]
67
+ pixel_size = (width_units / image_width) if image_width > 0 else 0.0
68
+ rows = []
69
+ for region in props:
70
+ area_px = float(region.area)
71
+ perimeter_px = float(region.perimeter)
72
+ major_px = float(region.major_axis_length) if region.major_axis_length else 0.0
73
+ minor_px = float(region.minor_axis_length) if region.minor_axis_length else 0.0
74
+ area = area_px * (pixel_size**2)
75
+ perimeter = perimeter_px * pixel_size
76
+ major = major_px * pixel_size
77
+ minor = minor_px * pixel_size
78
+ aspect_ratio = major / minor if minor > 0 else 0.0
79
+ circularity = (4 * np.pi * area / (perimeter**2)) if perimeter > 0 else 0.0
80
+ roundness = (4 * area / (np.pi * major**2)) if major > 0 else 0.0
81
+ region_mask = labels == region.label
82
+ region_mask = binary_closing(region_mask)
83
+ entropy_val = float(
84
+ shannon_entropy(region.intensity_image[region.image], base=2)
85
+ )
86
+ fractal_dim = box_counting_fd(region_mask)
87
+ integrated_density = float(region.intensity_image.sum()) * (pixel_size**2)
88
+ ecc_rel = float(region.eccentricity * major)
89
+ rows.append(
90
+ {
91
+ "Label": int(region.label),
92
+ "Area": area,
93
+ "Perimeter": perimeter,
94
+ "Aspect ratio": aspect_ratio,
95
+ "Circularity": circularity,
96
+ "Roundness": roundness,
97
+ "Entropy": entropy_val,
98
+ "Fractal dimension": fractal_dim,
99
+ "Integrated density": integrated_density,
100
+ "Eccentricity (rel width)": ecc_rel,
101
+ }
102
+ )
103
+ metrics_df = pd.DataFrame(rows)
104
+ avg_df = pd.DataFrame()
105
+ if not metrics_df.empty:
106
+ numeric_cols = metrics_df.columns.drop("Label")
107
+ avg_row = {"Metric": "Average"}
108
+ avg_row.update(metrics_df[numeric_cols].mean().to_dict())
109
+ avg_df = pd.DataFrame([avg_row])
110
+ return metrics_df, avg_df
111
+
112
+
113
+ def run_inference(image: np.ndarray, model_name: str, width_units: float):
114
+ if image is None:
115
+ return None, None, None
116
+ if width_units <= 0:
117
+ width_units = 1.0
118
+ model = get_model(model_name)
119
+ image_input = image.copy()
120
+ if model_name == "2D_versatile_fluo":
121
+ image_input = to_gray(image_input)
122
+ image_norm = normalize(image_input, 1, 99.8, axis=(0, 1))
123
+ labels, _ = model.predict_instances(image_norm)
124
+ overlay = render_label(labels, img=image)
125
+ if np.issubdtype(overlay.dtype, np.floating):
126
+ overlay = np.clip(overlay, 0, 1)
127
+ overlay = img_as_ubyte(overlay)
128
+ intensity_image = to_gray(image)
129
+ metrics_df, avg_df = compute_metrics(labels, intensity_image, width_units)
130
+ return overlay, metrics_df, avg_df
131
+
132
+
133
+ with gr.Blocks(title="StarDist 2D Segmentation - HF app by Ram Sevuggan") as demo:
134
+ gr.Markdown("# StarDist 2D Segmentation - HF app by Ram Sevuggan")
135
+ with gr.Row():
136
+ with gr.Column():
137
+ input_image = gr.Image(label="Input image", type="numpy")
138
+ model_dropdown = gr.Dropdown(
139
+ choices=MODEL_NAMES,
140
+ value="2D_versatile_fluo",
141
+ label="Model",
142
+ )
143
+ width_units = gr.Number(
144
+ value=1.0,
145
+ minimum=1e-6,
146
+ label="Image width (units)",
147
+ info="Used for eccentricity relative to image width",
148
+ )
149
+ run_button = gr.Button("Run")
150
+ with gr.Column():
151
+ output_image = gr.Image(label="Overlay", type="numpy")
152
+ metrics_table = gr.Dataframe(
153
+ label="Object metrics",
154
+ interactive=False,
155
+ )
156
+ avg_table = gr.Dataframe(
157
+ label="Average metrics",
158
+ interactive=False,
159
+ )
160
+
161
+ run_button.click(
162
+ fn=run_inference,
163
+ inputs=[input_image, model_dropdown, width_units],
164
+ outputs=[output_image, metrics_table, avg_table],
165
+ )
166
+
167
+ if __name__ == "__main__":
168
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.4.0
2
+ aiofiles==24.1.0
3
+ annotated-doc==0.0.4
4
+ annotated-types==0.7.0
5
+ anyio==4.12.1
6
+ astunparse==1.6.3
7
+ brotli==1.2.0
8
+ certifi==2026.2.25
9
+ charset-normalizer==3.4.4
10
+ click==8.3.1
11
+ contourpy==1.3.3
12
+ csbdeep==0.8.2
13
+ cycler==0.12.1
14
+ fastapi==0.135.1
15
+ ffmpy==1.0.0
16
+ filelock==3.25.0
17
+ flatbuffers==25.12.19
18
+ fonttools==4.61.1
19
+ fsspec==2026.2.0
20
+ gast==0.7.0
21
+ google-pasta==0.2.0
22
+ gradio==6.8.0
23
+ gradio-client==2.2.0
24
+ groovy==0.1.2
25
+ grpcio==1.78.0
26
+ h11==0.16.0
27
+ h5py==3.15.1
28
+ hf-xet==1.3.2
29
+ httpcore==1.0.9
30
+ httpx==0.28.1
31
+ huggingface-hub==1.5.0
32
+ idna==3.11
33
+ imageio==2.37.2
34
+ jinja2==3.1.6
35
+ keras==3.13.2
36
+ kiwisolver==1.4.9
37
+ lazy-loader==0.4
38
+ libclang==18.1.1
39
+ llvmlite==0.46.0
40
+ markdown==3.10.2
41
+ markdown-it-py==4.0.0
42
+ markupsafe==3.0.3
43
+ matplotlib==3.10.8
44
+ mdurl==0.1.2
45
+ ml-dtypes==0.5.4
46
+ namex==0.1.0
47
+ networkx==3.6.1
48
+ numba==0.64.0
49
+ numpy==2.4.2
50
+ opt-einsum==3.4.0
51
+ optree==0.19.0
52
+ orjson==3.11.7
53
+ packaging==26.0
54
+ pandas==3.0.1
55
+ pillow==12.1.1
56
+ protobuf==7.34.0
57
+ pydantic==2.12.5
58
+ pydantic-core==2.41.5
59
+ pydub==0.25.1
60
+ pygments==2.19.2
61
+ pyparsing==3.3.2
62
+ python-dateutil==2.9.0.post0
63
+ python-multipart==0.0.22
64
+ pytz==2025.2
65
+ pyyaml==6.0.3
66
+ requests==2.32.5
67
+ rich==14.3.3
68
+ safehttpx==0.1.7
69
+ scikit-image==0.26.0
70
+ scipy==1.17.1
71
+ semantic-version==2.10.0
72
+ setuptools==82.0.0
73
+ shellingham==1.5.4
74
+ six==1.17.0
75
+ stardist==0.9.2
76
+ starlette==0.52.1
77
+ tensorboard==2.20.0
78
+ tensorboard-data-server==0.7.2
79
+ tensorflow==2.20.0
80
+ termcolor==3.3.0
81
+ tifffile==2026.2.24
82
+ tomlkit==0.13.3
83
+ tqdm==4.67.3
84
+ typer==0.24.1
85
+ typing-extensions==4.15.0
86
+ typing-inspection==0.4.2
87
+ urllib3==2.6.3
88
+ uvicorn==0.41.0
89
+ werkzeug==3.1.6
90
+ wheel==0.46.3
91
+ wrapt==2.1.1