Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -216,69 +216,66 @@ def measure_confluency(masks, image_np):
|
|
| 216 |
confluency = cell_pixels / tot_pixels * 100
|
| 217 |
return confluency
|
| 218 |
|
| 219 |
-
def filter_mask_by_size(masks,minimum_pixels):
|
| 220 |
-
filtered_masks=masks.copy()
|
| 221 |
cell_ids = np.unique(masks)
|
| 222 |
cell_ids = cell_ids[cell_ids > 0]
|
| 223 |
|
| 224 |
removed_count = 0
|
| 225 |
-
|
| 226 |
for cell_id in cell_ids:
|
| 227 |
cell_mask = (masks == cell_id)
|
| 228 |
cell_pixels = np.count_nonzero(cell_mask)
|
| 229 |
-
|
| 230 |
if cell_pixels < minimum_pixels:
|
| 231 |
filtered_masks[cell_mask] = 0
|
| 232 |
-
removed_count +=1
|
| 233 |
|
| 234 |
unique_ids = np.unique(filtered_masks)
|
| 235 |
unique_ids = unique_ids[unique_ids > 0]
|
| 236 |
-
|
| 237 |
renumbered_masks = np.zeros_like(filtered_masks)
|
| 238 |
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 239 |
renumbered_masks[filtered_masks == old_id] = new_id
|
| 240 |
|
| 241 |
-
|
| 242 |
return renumbered_masks, removed_count
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
|
|
|
| 246 |
cell_ids = np.unique(masks)
|
| 247 |
cell_ids = cell_ids[cell_ids > 0]
|
| 248 |
|
| 249 |
removed_count = 0
|
| 250 |
-
|
| 251 |
for cell_id in cell_ids:
|
| 252 |
cell_mask = (masks == cell_id)
|
| 253 |
cell_pixels = np.count_nonzero(cell_mask)
|
| 254 |
-
|
| 255 |
if cell_pixels > maximum_pixels:
|
| 256 |
filtered_masks[cell_mask] = 0
|
| 257 |
-
removed_count +=1
|
| 258 |
|
| 259 |
unique_ids = np.unique(filtered_masks)
|
| 260 |
unique_ids = unique_ids[unique_ids > 0]
|
| 261 |
-
|
| 262 |
renumbered_masks = np.zeros_like(filtered_masks)
|
| 263 |
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 264 |
renumbered_masks[filtered_masks == old_id] = new_id
|
| 265 |
|
| 266 |
-
|
| 267 |
return renumbered_masks, removed_count
|
| 268 |
-
|
|
|
|
| 269 |
def rec_min_size(masks):
|
| 270 |
-
|
|
|
|
|
|
|
| 271 |
if num_cells <= 0:
|
| 272 |
return 0
|
| 273 |
mean_cell_size = np.count_nonzero(masks) / num_cells
|
| 274 |
return int(round(mean_cell_size))
|
| 275 |
|
|
|
|
| 276 |
@spaces.GPU
|
| 277 |
def run_segmentation_editor(editor_data, model_choice, min_cell_size, max_cell_size):
|
| 278 |
-
|
| 279 |
-
Runs cell segmentation using ImageEditor data.
|
| 280 |
-
Returns initial segmentation overlay, counts, confluency, and also masks/image for state.
|
| 281 |
-
"""
|
| 282 |
try:
|
| 283 |
model_filename = MODEL_OPTIONS[model_choice]
|
| 284 |
model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
|
|
@@ -294,7 +291,6 @@ def run_segmentation_editor(editor_data, model_choice, min_cell_size, max_cell_s
|
|
| 294 |
if region_np is None:
|
| 295 |
return 0, None, "No image provided.", gr.update(visible=False), None, None, 0.0, gr.update()
|
| 296 |
|
| 297 |
-
|
| 298 |
# Enforce mobile-safe size limit immediately
|
| 299 |
region_np = safe_resize(region_np)
|
| 300 |
|
|
@@ -307,55 +303,59 @@ def run_segmentation_editor(editor_data, model_choice, min_cell_size, max_cell_s
|
|
| 307 |
processed_image_np = region_np
|
| 308 |
|
| 309 |
# Run Cellpose segmentation
|
| 310 |
-
|
|
|
|
|
|
|
|
|
|
| 311 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
removed_small = 0
|
| 313 |
removed_large = 0
|
| 314 |
-
|
| 315 |
-
# Minimum size filtering
|
| 316 |
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
if min_cell_size > 0:
|
| 320 |
-
masks, removed_small = filter_mask_by_size(masks, min_cell_size)
|
| 321 |
-
filter_msg = f"Removed {removed_small} small objects (< {min_cell_size} pixels).\n"
|
| 322 |
-
else:
|
| 323 |
-
removed_small = 0
|
| 324 |
-
filter_msg=""
|
| 325 |
-
|
| 326 |
-
|
| 327 |
|
| 328 |
-
# Maximum size filtering
|
| 329 |
if max_cell_size > 0:
|
| 330 |
-
masks, removed_large = filter_mask_by_maxsize(masks, max_cell_size)
|
| 331 |
-
filter_msg = f"Removed {removed_large} large objects (> {max_cell_size} pixels).\n"
|
| 332 |
-
else:
|
| 333 |
-
removed_large = 0
|
| 334 |
-
filter_msg=""
|
| 335 |
|
| 336 |
-
removed_count = removed_small + removed_large
|
| 337 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
cell_count = len(np.unique(masks)) - 1
|
| 339 |
confluency = measure_confluency(masks, processed_image_np)
|
|
|
|
| 340 |
# Create a basic segmentation overlay (without viability)
|
| 341 |
segmentation_overlay = processed_image_np.copy().astype(np.float32)
|
| 342 |
if masks.max() > 0:
|
| 343 |
np.random.seed(42) # For consistent random colors
|
| 344 |
colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
|
| 345 |
-
colors[0] = [0, 0, 0]
|
| 346 |
colored_mask = colors[masks]
|
| 347 |
alpha = 0.4
|
| 348 |
segmentation_overlay = (1 - alpha) * segmentation_overlay + alpha * colored_mask
|
| 349 |
segmentation_overlay = np.clip(segmentation_overlay, 0, 255).astype(np.uint8)
|
| 350 |
|
| 351 |
-
info_msg =
|
|
|
|
|
|
|
|
|
|
| 352 |
info_msg += f"Confluency: {confluency:.1f}%\n"
|
| 353 |
-
info_msg = filter_msg + info_msg
|
| 354 |
if region_coords:
|
| 355 |
-
info_msg +=
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
-
# Return initial segmentation display and state variables (packed for Gradio State)
|
| 359 |
return (
|
| 360 |
cell_count,
|
| 361 |
Image.fromarray(segmentation_overlay),
|
|
@@ -364,12 +364,20 @@ def run_segmentation_editor(editor_data, model_choice, min_cell_size, max_cell_s
|
|
| 364 |
pack_array(masks),
|
| 365 |
pack_array(processed_image_np),
|
| 366 |
confluency,
|
| 367 |
-
gr.update(value
|
| 368 |
)
|
| 369 |
|
| 370 |
except Exception as e:
|
| 371 |
-
return
|
| 372 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
|
| 374 |
|
| 375 |
|
|
@@ -448,9 +456,9 @@ with gr.Blocks(
|
|
| 448 |
min_size_slider1 = gr.Slider(
|
| 449 |
minimum=0,
|
| 450 |
maximum=500,
|
| 451 |
-
value=
|
| 452 |
step=10,
|
| 453 |
-
label="Minimum Cell Size (pixels)",
|
| 454 |
|
| 455 |
)
|
| 456 |
max_size_slider1 = gr.Slider(
|
|
|
|
| 216 |
confluency = cell_pixels / tot_pixels * 100
|
| 217 |
return confluency
|
| 218 |
|
| 219 |
+
def filter_mask_by_size(masks, minimum_pixels):
|
| 220 |
+
filtered_masks = masks.copy()
|
| 221 |
cell_ids = np.unique(masks)
|
| 222 |
cell_ids = cell_ids[cell_ids > 0]
|
| 223 |
|
| 224 |
removed_count = 0
|
| 225 |
+
|
| 226 |
for cell_id in cell_ids:
|
| 227 |
cell_mask = (masks == cell_id)
|
| 228 |
cell_pixels = np.count_nonzero(cell_mask)
|
|
|
|
| 229 |
if cell_pixels < minimum_pixels:
|
| 230 |
filtered_masks[cell_mask] = 0
|
| 231 |
+
removed_count += 1
|
| 232 |
|
| 233 |
unique_ids = np.unique(filtered_masks)
|
| 234 |
unique_ids = unique_ids[unique_ids > 0]
|
| 235 |
+
|
| 236 |
renumbered_masks = np.zeros_like(filtered_masks)
|
| 237 |
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 238 |
renumbered_masks[filtered_masks == old_id] = new_id
|
| 239 |
|
|
|
|
| 240 |
return renumbered_masks, removed_count
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def filter_mask_by_maxsize(masks, maximum_pixels):
|
| 244 |
+
filtered_masks = masks.copy()
|
| 245 |
cell_ids = np.unique(masks)
|
| 246 |
cell_ids = cell_ids[cell_ids > 0]
|
| 247 |
|
| 248 |
removed_count = 0
|
|
|
|
| 249 |
for cell_id in cell_ids:
|
| 250 |
cell_mask = (masks == cell_id)
|
| 251 |
cell_pixels = np.count_nonzero(cell_mask)
|
|
|
|
| 252 |
if cell_pixels > maximum_pixels:
|
| 253 |
filtered_masks[cell_mask] = 0
|
| 254 |
+
removed_count += 1
|
| 255 |
|
| 256 |
unique_ids = np.unique(filtered_masks)
|
| 257 |
unique_ids = unique_ids[unique_ids > 0]
|
| 258 |
+
|
| 259 |
renumbered_masks = np.zeros_like(filtered_masks)
|
| 260 |
for new_id, old_id in enumerate(unique_ids, start=1):
|
| 261 |
renumbered_masks[filtered_masks == old_id] = new_id
|
| 262 |
|
|
|
|
| 263 |
return renumbered_masks, removed_count
|
| 264 |
+
|
| 265 |
+
|
| 266 |
def rec_min_size(masks):
|
| 267 |
+
ids = np.unique(masks)
|
| 268 |
+
ids = ids[ids > 0]
|
| 269 |
+
num_cells = len(ids)
|
| 270 |
if num_cells <= 0:
|
| 271 |
return 0
|
| 272 |
mean_cell_size = np.count_nonzero(masks) / num_cells
|
| 273 |
return int(round(mean_cell_size))
|
| 274 |
|
| 275 |
+
|
| 276 |
@spaces.GPU
|
| 277 |
def run_segmentation_editor(editor_data, model_choice, min_cell_size, max_cell_size):
|
| 278 |
+
|
|
|
|
|
|
|
|
|
|
| 279 |
try:
|
| 280 |
model_filename = MODEL_OPTIONS[model_choice]
|
| 281 |
model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
|
|
|
|
| 291 |
if region_np is None:
|
| 292 |
return 0, None, "No image provided.", gr.update(visible=False), None, None, 0.0, gr.update()
|
| 293 |
|
|
|
|
| 294 |
# Enforce mobile-safe size limit immediately
|
| 295 |
region_np = safe_resize(region_np)
|
| 296 |
|
|
|
|
| 303 |
processed_image_np = region_np
|
| 304 |
|
| 305 |
# Run Cellpose segmentation
|
| 306 |
+
masks_raw, flows, styles = model.eval(processed_image_np, diameter=None, channels=[0, 0])
|
| 307 |
+
|
| 308 |
+
# Compute recommendation from RAW masks
|
| 309 |
+
recommend_min = rec_min_size(masks_raw)
|
| 310 |
|
| 311 |
+
|
| 312 |
+
# If user sets slider to 0, use the recommendation
|
| 313 |
+
min_used = recommend_min if (min_cell_size == 0) else int(min_cell_size)
|
| 314 |
+
|
| 315 |
+
# Apply filters
|
| 316 |
+
masks = masks_raw
|
| 317 |
removed_small = 0
|
| 318 |
removed_large = 0
|
|
|
|
|
|
|
| 319 |
|
| 320 |
+
if min_used > 0:
|
| 321 |
+
masks, removed_small = filter_mask_by_size(masks, min_used)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
|
|
|
| 323 |
if max_cell_size > 0:
|
| 324 |
+
masks, removed_large = filter_mask_by_maxsize(masks, int(max_cell_size))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
|
|
|
|
| 326 |
|
| 327 |
+
filter_msg = ""
|
| 328 |
+
if removed_small:
|
| 329 |
+
filter_msg += f"Removed {removed_small} small objects (< {min_used} pixels).\n"
|
| 330 |
+
if removed_large:
|
| 331 |
+
filter_msg += f"Removed {removed_large} large objects (> {int(max_cell_size)} pixels).\n"
|
| 332 |
+
|
| 333 |
cell_count = len(np.unique(masks)) - 1
|
| 334 |
confluency = measure_confluency(masks, processed_image_np)
|
| 335 |
+
|
| 336 |
# Create a basic segmentation overlay (without viability)
|
| 337 |
segmentation_overlay = processed_image_np.copy().astype(np.float32)
|
| 338 |
if masks.max() > 0:
|
| 339 |
np.random.seed(42) # For consistent random colors
|
| 340 |
colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
|
| 341 |
+
colors[0] = [0, 0, 0]
|
| 342 |
colored_mask = colors[masks]
|
| 343 |
alpha = 0.4
|
| 344 |
segmentation_overlay = (1 - alpha) * segmentation_overlay + alpha * colored_mask
|
| 345 |
segmentation_overlay = np.clip(segmentation_overlay, 0, 255).astype(np.uint8)
|
| 346 |
|
| 347 |
+
info_msg = ""
|
| 348 |
+
if filter_msg:
|
| 349 |
+
info_msg += filter_msg
|
| 350 |
+
info_msg += f"Segmentation complete! Found {cell_count} cells.\n"
|
| 351 |
info_msg += f"Confluency: {confluency:.1f}%\n"
|
|
|
|
| 352 |
if region_coords:
|
| 353 |
+
info_msg += (
|
| 354 |
+
f"Processed region: {region_coords[0]},{region_coords[1]} "
|
| 355 |
+
f"to {region_coords[2]},{region_coords[3]}\n"
|
| 356 |
+
)
|
| 357 |
+
info_msg += "Now adjust the Blue Threshold for viability assessment."
|
| 358 |
|
|
|
|
| 359 |
return (
|
| 360 |
cell_count,
|
| 361 |
Image.fromarray(segmentation_overlay),
|
|
|
|
| 364 |
pack_array(masks),
|
| 365 |
pack_array(processed_image_np),
|
| 366 |
confluency,
|
| 367 |
+
gr.update(value=recommend_min), # update slider display to recommended
|
| 368 |
)
|
| 369 |
|
| 370 |
except Exception as e:
|
| 371 |
+
return (
|
| 372 |
+
0,
|
| 373 |
+
None,
|
| 374 |
+
f"Error during segmentation: {str(e)}",
|
| 375 |
+
gr.update(visible=False),
|
| 376 |
+
None,
|
| 377 |
+
None,
|
| 378 |
+
0.0,
|
| 379 |
+
gr.update(),
|
| 380 |
+
)
|
| 381 |
|
| 382 |
|
| 383 |
|
|
|
|
| 456 |
min_size_slider1 = gr.Slider(
|
| 457 |
minimum=0,
|
| 458 |
maximum=500,
|
| 459 |
+
value=0,
|
| 460 |
step=10,
|
| 461 |
+
label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
|
| 462 |
|
| 463 |
)
|
| 464 |
max_size_slider1 = gr.Slider(
|