Update app.py
Browse files
app.py
CHANGED
|
@@ -128,31 +128,64 @@ def measure_confluency(masks, image_np):
|
|
| 128 |
cell_pixels = np.count_nonzero(masks)
|
| 129 |
confluency = cell_pixels / tot_pixels * 100
|
| 130 |
return confluency
|
| 131 |
-
|
| 132 |
-
def filter_mask_by_size(masks,minimum_pixels):
|
| 133 |
-
filtered_masks=masks.copy()
|
| 134 |
cell_ids = np.unique(masks)
|
| 135 |
cell_ids = cell_ids[cell_ids > 0]
|
| 136 |
|
| 137 |
removed_count = 0
|
| 138 |
-
|
| 139 |
for cell_id in cell_ids:
|
| 140 |
cell_mask = (masks == cell_id)
|
| 141 |
cell_pixels = np.count_nonzero(cell_mask)
|
| 142 |
-
|
| 143 |
if cell_pixels < minimum_pixels:
|
| 144 |
filtered_masks[cell_mask] = 0
|
| 145 |
-
removed_count +=1
|
| 146 |
|
| 147 |
unique_ids = np.unique(filtered_masks)
|
| 148 |
unique_ids = unique_ids[unique_ids > 0]
|
| 149 |
-
|
| 150 |
renumbered_masks = np.zeros_like(filtered_masks)
|
| 151 |
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 152 |
renumbered_masks[filtered_masks == old_id] = new_id
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
return renumbered_masks, removed_count
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
@spaces.GPU
|
| 157 |
def run_segmentation(image, model_choice,min_cell_size):
|
| 158 |
image_np = np.array(image)
|
|
@@ -182,13 +215,41 @@ def run_segmentation(image, model_choice,min_cell_size):
|
|
| 182 |
# Run Cellpose segmentation
|
| 183 |
masks, flows, styles = model.eval(processed_image_np, diameter=None, channels=[0, 0])
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
cell_count = len(np.unique(masks)) - 1
|
| 193 |
confluency = measure_confluency(masks, processed_image_np)
|
| 194 |
|
|
@@ -197,17 +258,24 @@ def run_segmentation(image, model_choice,min_cell_size):
|
|
| 197 |
if masks.max() > 0:
|
| 198 |
np.random.seed(42) # For consistent random colors
|
| 199 |
colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
|
| 200 |
-
colors[0] = [0, 0, 0]
|
| 201 |
colored_mask = colors[masks]
|
| 202 |
alpha = 0.4
|
| 203 |
segmentation_overlay = (1 - alpha) * segmentation_overlay + alpha * colored_mask
|
| 204 |
segmentation_overlay = np.clip(segmentation_overlay, 0, 255).astype(np.uint8)
|
| 205 |
|
| 206 |
-
info_msg =
|
|
|
|
|
|
|
|
|
|
| 207 |
info_msg += f"Confluency: {confluency:.1f}%\n"
|
| 208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
|
| 210 |
-
# Return initial segmentation display and state variables (packed for Gradio State)
|
| 211 |
return (
|
| 212 |
cell_count,
|
| 213 |
Image.fromarray(segmentation_overlay),
|
|
@@ -216,10 +284,22 @@ def run_segmentation(image, model_choice,min_cell_size):
|
|
| 216 |
pack_array(masks),
|
| 217 |
pack_array(processed_image_np),
|
| 218 |
confluency,
|
|
|
|
| 219 |
)
|
| 220 |
|
| 221 |
except Exception as e:
|
| 222 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
|
| 225 |
|
|
@@ -294,11 +374,20 @@ with gr.Blocks(
|
|
| 294 |
min_size_slider1 = gr.Slider(
|
| 295 |
minimum=0,
|
| 296 |
maximum=500,
|
| 297 |
-
value=
|
| 298 |
step=10,
|
| 299 |
-
label="Minimum Cell Size (pixels)",
|
| 300 |
|
| 301 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
|
| 303 |
|
| 304 |
with gr.Column():
|
|
@@ -334,8 +423,8 @@ with gr.Blocks(
|
|
| 334 |
# Event handlers
|
| 335 |
segment_btn1.click(
|
| 336 |
fn=run_segmentation,
|
| 337 |
-
inputs=[img_input, model_dropdown1,min_size_slider1],
|
| 338 |
-
outputs=[cell_count_output1, overlay_output1, info_output1, viability_section1, masks_state, image_state, confluency_output1]
|
| 339 |
).then( # Chain the initial viability assessment after segmentation
|
| 340 |
fn=update_viability_realtime,
|
| 341 |
inputs=[blue_threshold1, masks_state, image_state], # Pass stored state as inputs
|
|
|
|
| 128 |
cell_pixels = np.count_nonzero(masks)
|
| 129 |
confluency = cell_pixels / tot_pixels * 100
|
| 130 |
return confluency
|
| 131 |
+
|
| 132 |
+
def filter_mask_by_size(masks, minimum_pixels):
|
| 133 |
+
filtered_masks = masks.copy()
|
| 134 |
cell_ids = np.unique(masks)
|
| 135 |
cell_ids = cell_ids[cell_ids > 0]
|
| 136 |
|
| 137 |
removed_count = 0
|
| 138 |
+
|
| 139 |
for cell_id in cell_ids:
|
| 140 |
cell_mask = (masks == cell_id)
|
| 141 |
cell_pixels = np.count_nonzero(cell_mask)
|
|
|
|
| 142 |
if cell_pixels < minimum_pixels:
|
| 143 |
filtered_masks[cell_mask] = 0
|
| 144 |
+
removed_count += 1
|
| 145 |
|
| 146 |
unique_ids = np.unique(filtered_masks)
|
| 147 |
unique_ids = unique_ids[unique_ids > 0]
|
| 148 |
+
|
| 149 |
renumbered_masks = np.zeros_like(filtered_masks)
|
| 150 |
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 151 |
renumbered_masks[filtered_masks == old_id] = new_id
|
| 152 |
+
|
| 153 |
+
return renumbered_masks, removed_count
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def filter_mask_by_maxsize(masks, maximum_pixels):
|
| 157 |
+
filtered_masks = masks.copy()
|
| 158 |
+
cell_ids = np.unique(masks)
|
| 159 |
+
cell_ids = cell_ids[cell_ids > 0]
|
| 160 |
+
|
| 161 |
+
removed_count = 0
|
| 162 |
+
for cell_id in cell_ids:
|
| 163 |
+
cell_mask = (masks == cell_id)
|
| 164 |
+
cell_pixels = np.count_nonzero(cell_mask)
|
| 165 |
+
if cell_pixels > maximum_pixels:
|
| 166 |
+
filtered_masks[cell_mask] = 0
|
| 167 |
+
removed_count += 1
|
| 168 |
+
|
| 169 |
+
unique_ids = np.unique(filtered_masks)
|
| 170 |
+
unique_ids = unique_ids[unique_ids > 0]
|
| 171 |
+
|
| 172 |
+
renumbered_masks = np.zeros_like(filtered_masks)
|
| 173 |
+
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 174 |
+
renumbered_masks[filtered_masks == old_id] = new_id
|
| 175 |
+
|
| 176 |
return renumbered_masks, removed_count
|
| 177 |
|
| 178 |
+
|
| 179 |
+
def rec_min_size(masks, q=25):
|
| 180 |
+
ids = np.unique(masks)
|
| 181 |
+
ids = ids[ids > 0]
|
| 182 |
+
if len(ids) == 0:
|
| 183 |
+
return 0
|
| 184 |
+
sizes = np.array([np.count_nonzero(masks == cid) for cid in ids])
|
| 185 |
+
return int(round(np.percentile(sizes, q)))
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
|
| 189 |
@spaces.GPU
|
| 190 |
def run_segmentation(image, model_choice,min_cell_size):
|
| 191 |
image_np = np.array(image)
|
|
|
|
| 215 |
# Run Cellpose segmentation
|
| 216 |
masks, flows, styles = model.eval(processed_image_np, diameter=None, channels=[0, 0])
|
| 217 |
|
| 218 |
+
ids = np.unique(masks_raw)
|
| 219 |
+
ids = ids[ids > 0]
|
| 220 |
+
|
| 221 |
+
sizes = np.array([np.count_nonzero(masks_raw == cid) for cid in ids])
|
| 222 |
+
|
| 223 |
+
print("num_cells:", len(ids))
|
| 224 |
+
print("mean:", sizes.mean())
|
| 225 |
+
print("median:", np.median(sizes))
|
| 226 |
+
print("p90:", np.percentile(sizes, 90))
|
| 227 |
+
print("max:", sizes.max())
|
| 228 |
+
# Compute recommendation from RAW masks
|
| 229 |
+
recommend_min = rec_min_size(masks_raw)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
# If user sets slider to 0, use the recommendation
|
| 233 |
+
min_used = recommend_min if (min_cell_size == 0) else int(min_cell_size)
|
| 234 |
+
|
| 235 |
+
# Apply filters
|
| 236 |
+
masks = masks_raw
|
| 237 |
+
removed_small = 0
|
| 238 |
+
removed_large = 0
|
| 239 |
+
|
| 240 |
+
if min_used > 0:
|
| 241 |
+
masks, removed_small = filter_mask_by_size(masks, min_used)
|
| 242 |
+
|
| 243 |
+
if max_cell_size > 0:
|
| 244 |
+
masks, removed_large = filter_mask_by_maxsize(masks, int(max_cell_size))
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
filter_msg = ""
|
| 248 |
+
if removed_small:
|
| 249 |
+
filter_msg += f"Removed {removed_small} small objects (< {min_used} pixels).\n"
|
| 250 |
+
if removed_large:
|
| 251 |
+
filter_msg += f"Removed {removed_large} large objects (> {int(max_cell_size)} pixels).\n"
|
| 252 |
+
|
| 253 |
cell_count = len(np.unique(masks)) - 1
|
| 254 |
confluency = measure_confluency(masks, processed_image_np)
|
| 255 |
|
|
|
|
| 258 |
if masks.max() > 0:
|
| 259 |
np.random.seed(42) # For consistent random colors
|
| 260 |
colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
|
| 261 |
+
colors[0] = [0, 0, 0]
|
| 262 |
colored_mask = colors[masks]
|
| 263 |
alpha = 0.4
|
| 264 |
segmentation_overlay = (1 - alpha) * segmentation_overlay + alpha * colored_mask
|
| 265 |
segmentation_overlay = np.clip(segmentation_overlay, 0, 255).astype(np.uint8)
|
| 266 |
|
| 267 |
+
info_msg = ""
|
| 268 |
+
if filter_msg:
|
| 269 |
+
info_msg += filter_msg
|
| 270 |
+
info_msg += f"Segmentation complete! Found {cell_count} cells.\n"
|
| 271 |
info_msg += f"Confluency: {confluency:.1f}%\n"
|
| 272 |
+
if region_coords:
|
| 273 |
+
info_msg += (
|
| 274 |
+
f"Processed region: {region_coords[0]},{region_coords[1]} "
|
| 275 |
+
f"to {region_coords[2]},{region_coords[3]}\n"
|
| 276 |
+
)
|
| 277 |
+
info_msg += "Now adjust the Blue Threshold for viability assessment."
|
| 278 |
|
|
|
|
| 279 |
return (
|
| 280 |
cell_count,
|
| 281 |
Image.fromarray(segmentation_overlay),
|
|
|
|
| 284 |
pack_array(masks),
|
| 285 |
pack_array(processed_image_np),
|
| 286 |
confluency,
|
| 287 |
+
gr.update(value=recommend_min), # update slider display to recommended
|
| 288 |
)
|
| 289 |
|
| 290 |
except Exception as e:
|
| 291 |
+
return (
|
| 292 |
+
0,
|
| 293 |
+
None,
|
| 294 |
+
f"Error during segmentation: {str(e)}",
|
| 295 |
+
gr.update(visible=False),
|
| 296 |
+
None,
|
| 297 |
+
None,
|
| 298 |
+
0.0,
|
| 299 |
+
gr.update(),
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
|
| 303 |
|
| 304 |
|
| 305 |
|
|
|
|
| 374 |
min_size_slider1 = gr.Slider(
|
| 375 |
minimum=0,
|
| 376 |
maximum=500,
|
| 377 |
+
value=0,
|
| 378 |
step=10,
|
| 379 |
+
label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
|
| 380 |
|
| 381 |
)
|
| 382 |
+
max_size_slider1 = gr.Slider(
|
| 383 |
+
minimum=0,
|
| 384 |
+
maximum=500,
|
| 385 |
+
value=500,
|
| 386 |
+
step=10,
|
| 387 |
+
label="Maximum Cell Size (pixels)",
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
|
| 391 |
segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
|
| 392 |
|
| 393 |
with gr.Column():
|
|
|
|
| 423 |
# Event handlers
|
| 424 |
segment_btn1.click(
|
| 425 |
fn=run_segmentation,
|
| 426 |
+
inputs=[img_input, model_dropdown1,min_size_slider1, max_size_slider1],
|
| 427 |
+
outputs=[cell_count_output1, overlay_output1, info_output1, viability_section1, masks_state, image_state, confluency_output1, min_size_slider1]
|
| 428 |
).then( # Chain the initial viability assessment after segmentation
|
| 429 |
fn=update_viability_realtime,
|
| 430 |
inputs=[blue_threshold1, masks_state, image_state], # Pass stored state as inputs
|