LiangLabUMB commited on
Commit
0ef14cd
·
verified ·
1 Parent(s): 4d45408

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +498 -1
app.py CHANGED
@@ -80,4 +80,501 @@ def extract_region_from_editor(editor_data):
80
 
81
  pad = 5
82
  h, w = background_np.shape[:2]
83
- y_min = max(0, y_min - pad)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  pad = 5
82
  h, w = background_np.shape[:2]
83
+ y_min = max(0, y_min - pad)
84
+ y_max = min(h, y_max + pad)
85
+ x_min = max(0, x_min - pad)
86
+ x_max = min(w, x_max + pad)
87
+
88
+ region = background_np[y_min:y_max+1, x_min:x_max+1]
89
+ print(f"DEBUG: extract_region_from_editor - Extracted region shape: {region.shape}")
90
+ return region, (x_min, y_min, x_max, y_max)
91
+ print("DEBUG: extract_region_from_editor - No selection or empty selection, returning full background.")
92
+ return background_np, None
93
+ else: # Assuming it's a direct PIL Image if not dict
94
+ if hasattr(editor_data, 'size'):
95
+ image_np = np.array(editor_data)
96
+ print(f"DEBUG: extract_region_from_editor - Direct PIL image, shape: {image_np.shape}")
97
+ return image_np, None
98
+ else:
99
+ print("DEBUG: extract_region_from_editor - editor_data is not dict and no size attribute, returning None.")
100
+ return None, None
101
+
102
+ def calculate_blue_intensity(image_np, masks):
103
+ """Calculate blue intensity for each cell"""
104
+ # Ensure image is in correct format
105
+ if len(image_np.shape) == 2:
106
+ # Convert grayscale to RGB
107
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
108
+ elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
109
+ # Handle RGBA
110
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
111
+
112
+ # Calculate blue intensity for each cell
113
+ cell_blue_intensities = {}
114
+ unique_cells = np.unique(masks)
115
+
116
+ print(f"DEBUG: Found {len(unique_cells)} unique mask values: {unique_cells}")
117
+ print(f"DEBUG: Image shape: {image_np.shape}, dtype: {image_np.dtype}")
118
+ print(f"DEBUG: Masks shape: {masks.shape}, dtype: {masks.dtype}")
119
+
120
+ for cell_id in unique_cells:
121
+ if cell_id == 0: # Skip background
122
+ continue
123
+
124
+ cell_mask = masks == cell_id
125
+ cell_pixels = image_np[cell_mask]
126
+
127
+ if len(cell_pixels) > 0:
128
+ # Calculate mean blue intensity (blue channel is index 2)
129
+ blue_intensity = np.mean(cell_pixels[:, 2]) # Blue channel
130
+ cell_blue_intensities[cell_id] = blue_intensity
131
+ # print(f"DEBUG: Cell {cell_id}: {len(cell_pixels)} pixels, blue intensity: {blue_intensity:.2f}") # Too verbose, uncomment if needed
132
+ print(f"DEBUG: Total cells with blue intensity: {len(cell_blue_intensities)}")
133
+ return cell_blue_intensities
134
+
135
+ def create_viability_overlay(image_np, masks, blue_threshold):
136
+ """Create overlay showing live/dead cells based on blue threshold"""
137
+
138
+ if masks is None or image_np is None:
139
+ print("DEBUG: create_viability_overlay - masks or image_np is None, returning None.")
140
+ return None, 0, 0
141
+
142
+ print(f"DEBUG: create_viability_overlay called with threshold: {blue_threshold}")
143
+ print(f"DEBUG: Image shape: {image_np.shape}, Masks shape: {masks.shape}, Masks max: {masks.max()}")
144
+
145
+ # Ensure image is RGB
146
+ if len(image_np.shape) == 2:
147
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
148
+ elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
149
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
150
+
151
+ # Calculate blue intensities
152
+ cell_blue_intensities = calculate_blue_intensity(image_np, masks)
153
+
154
+ print(f"DEBUG: Got {len(cell_blue_intensities)} cells with blue intensities")
155
+
156
+ # Create overlay - start with original image
157
+ overlay = image_np.copy().astype(np.float32)
158
+
159
+ live_count = 0
160
+ dead_count = 0
161
+
162
+ # Color cells based on viability
163
+ for cell_id, blue_intensity in cell_blue_intensities.items():
164
+ cell_mask = masks == cell_id
165
+
166
+ # print(f"DEBUG: Processing cell {cell_id}, blue intensity: {blue_intensity:.2f}, threshold: {blue_threshold}") # Too verbose
167
+
168
+ if blue_intensity > blue_threshold:
169
+ # Dead cell - color in red
170
+ overlay[cell_mask] = [255, 0, 0] # Red
171
+ dead_count += 1
172
+ # print(f"DEBUG: Cell {cell_id} classified as DEAD") # Too verbose
173
+ else:
174
+ # Live cell - color in green
175
+ overlay[cell_mask] = [0, 255, 0] # Green
176
+ live_count += 1
177
+ # print(f"DEBUG: Cell {cell_id} classified as LIVE") # Too verbose
178
+ print(f"DEBUG: Final counts - Live: {live_count}, Dead: {dead_count}")
179
+
180
+ # Blend with original image
181
+ alpha = 0.6 # Increased alpha for better visibility
182
+ final_overlay = (1 - alpha) * image_np.astype(np.float32) + alpha * overlay
183
+ final_overlay = np.clip(final_overlay, 0, 255).astype(np.uint8)
184
+
185
+ return Image.fromarray(final_overlay), live_count, dead_count
186
+
187
+ @spaces.GPU
188
+ def segment_cells(editor_data, model_choice): # crop_coords removed from here, handled within extract_region_from_editor implicitly by ImageEditor
189
+ """
190
+ First stage: Segment cells and return results for viability assessment
191
+ """
192
+ global current_region_coords # Keep global for info display if needed later
193
+
194
+ try:
195
+ print("DEBUG: --- Starting segment_cells ---")
196
+ debug_info = debug_editor_data(editor_data)
197
+ print(f"DEBUG: Editor data info:\n{debug_info}")
198
+
199
+ # Load the model
200
+ model_filename = MODEL_OPTIONS[model_choice]
201
+ model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
202
+
203
+ if model_filename in loaded_models:
204
+ model = loaded_models[model_filename]
205
+ else:
206
+ model = models.CellposeModel(gpu=True, pretrained_model=model_path)
207
+ loaded_models[model_filename] = model
208
+
209
+ # Extract region from editor
210
+ region_np, region_coords = extract_region_from_editor(editor_data)
211
+
212
+ if region_np is None:
213
+ print("DEBUG: region_np is None, returning error and hiding viability section.")
214
+ current_region_coords = None
215
+ # Also return None for masks and image for the gr.State components
216
+ return 0, None, f"No image provided. Debug info:\n{debug_info}", gr.update(visible=False), None, None
217
+
218
+ print(f"DEBUG: Image to segment shape (after extraction/cropping): {region_np.shape}, dtype: {region_np.dtype}")
219
+
220
+ # Process image format
221
+ if len(region_np.shape) == 2:
222
+ region_np_processed = cv2.cvtColor(region_np, cv2.COLOR_GRAY2RGB)
223
+ elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
224
+ region_np_processed = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
225
+ else:
226
+ region_np_processed = region_np # Already RGB or suitable
227
+
228
+ print(f"DEBUG: Image before model.eval shape: {region_np_processed.shape}, dtype: {region_np_processed.dtype}")
229
+
230
+ # Run Cellpose segmentation
231
+ masks, flows, styles = model.eval(region_np_processed, diameter=None, channels=[0, 0])
232
+ print(f"DEBUG: After model.eval: masks type: {type(masks)}, shape: {masks.shape if hasattr(masks, 'shape') else 'N/A'}")
233
+
234
+ # Update global region coords for info display
235
+ current_region_coords = region_coords
236
+
237
+ # Count unique cells
238
+ cell_count = len(np.unique(masks)) - 1
239
+ print(f"DEBUG: Cell count: {cell_count}")
240
+
241
+ # Create basic segmentation overlay
242
+ overlay = region_np_processed.copy().astype(np.float32)
243
+
244
+ if masks.max() > 0:
245
+ np.random.seed(42)
246
+ colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
247
+ colors[0] = [0, 0, 0] # Background stays black
248
+
249
+ colored_mask = colors[masks]
250
+ alpha = 0.4
251
+ overlay = (1 - alpha) * overlay + alpha * colored_mask
252
+
253
+ overlay = np.clip(overlay, 0, 255).astype(np.uint8)
254
+ overlay_image = Image.fromarray(overlay)
255
+
256
+ info_msg = f"Segmentation complete! Found {cell_count} cells.\n"
257
+ if region_coords:
258
+ info_msg += f"Region: {region_coords}\n"
259
+ info_msg += f"Now use the blue threshold slider below for viability assessment."
260
+
261
+ # Make viability section visible
262
+ viability_section_update = gr.update(visible=True)
263
+ print("DEBUG: segment_cells returning successfully, viability section visible.")
264
+
265
+ # Return masks and original processed image for state components
266
+ return cell_count, overlay_image, info_msg, viability_section_update, masks, region_np_processed
267
+
268
+ except Exception as e:
269
+ print(f"ERROR: An exception occurred in segment_cells: {e}")
270
+ current_region_coords = None
271
+ # Return None for masks and image in case of error
272
+ return 0, None, f"Error: {str(e)}", gr.update(visible=False), None, None
273
+
274
+ def update_viability_assessment(blue_threshold, stored_masks, stored_image):
275
+ """
276
+ Real-time viability assessment based on blue threshold
277
+ """
278
+
279
+ print("DEBUG: --- Starting update_viability_assessment ---")
280
+ print(f"DEBUG: At start of update_viability_assessment: stored_masks is None? {stored_masks is None}, stored_image is None? {stored_image is None}")
281
+
282
+ if stored_masks is None or stored_image is None:
283
+ print("DEBUG: Passed-in stored_masks or stored_image is None, returning 'Please run segmentation first'.")
284
+ return None, 0, 0, "Please run segmentation first"
285
+
286
+ try:
287
+ # Debug information
288
+ unique_cells = np.unique(stored_masks)
289
+ total_cells = len(unique_cells) - 1 # Subtract background
290
+
291
+ debug_info = f"Debug - Total cells found: {total_cells}\n"
292
+ debug_info += f"Image shape: {stored_image.shape}\n"
293
+ debug_info += f"Masks shape: {stored_masks.shape}\n"
294
+ debug_info += f"Unique mask values (first 10): {unique_cells[:10]}...\n"
295
+
296
+ # Create viability overlay
297
+ viability_overlay, live_count, dead_count = create_viability_overlay(
298
+ stored_image, stored_masks, blue_threshold
299
+ )
300
+
301
+ total_count = live_count + dead_count
302
+ viability_info = f"Blue threshold: {blue_threshold}\n"
303
+ viability_info += f"Total cells: {total_count}\nLive (green): {live_count}\nDead (red): {dead_count}\n"
304
+
305
+ if total_count > 0:
306
+ viability_percentage = (live_count / total_count) * 100
307
+ viability_info += f"Viability: {viability_percentage:.1f}%\n"
308
+
309
+ viability_info += f"\n{debug_info}"
310
+ print("DEBUG: update_viability_assessment returning successfully.")
311
+
312
+ return viability_overlay, live_count, dead_count, viability_info
313
+
314
+ except Exception as e:
315
+ print(f"ERROR: An exception occurred in update_viability_assessment: {e}")
316
+ return None, 0, 0, f"Error in viability assessment: {str(e)}\nMasks available: {stored_masks is not None}\nImage available: {stored_image is not None}"
317
+
318
+ @spaces.GPU
319
+ def segment_with_coords(image, model_choice, crop_coords):
320
+ """Alternative function for manual coordinates input"""
321
+ global current_region_coords
322
+
323
+ print("DEBUG: --- Starting segment_with_coords ---")
324
+ if image is None:
325
+ print("DEBUG: No image provided for segment_with_coords, returning error.")
326
+ current_region_coords = None
327
+ return 0, None, "No image provided", gr.update(visible=False), None, None
328
+
329
+ try:
330
+ # Load the model
331
+ model_filename = MODEL_OPTIONS[model_choice]
332
+ model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
333
+
334
+ if model_filename in loaded_models:
335
+ model = loaded_models[model_filename]
336
+ else:
337
+ model = models.CellposeModel(gpu=True, pretrained_model=model_path)
338
+ loaded_models[model_filename] = model
339
+
340
+ # Convert PIL Image to numpy array
341
+ image_np = np.array(image)
342
+ print(f"DEBUG: image_np from PIL: shape={image_np.shape}, dtype={image_np.dtype}")
343
+
344
+ # Apply crop if coordinates are provided
345
+ if crop_coords and crop_coords.strip():
346
+ try:
347
+ coords = [int(x.strip()) for x in crop_coords.split(',')]
348
+ if len(coords) == 4:
349
+ x_min, y_min, x_max, y_max = coords
350
+ h, w = image_np.shape[:2]
351
+ x_min = max(0, min(x_min, w-1))
352
+ y_min = max(0, min(y_min, h-1))
353
+ x_max = max(x_min+1, min(x_max, w))
354
+ y_max = max(y_min+1, min(y_max, h))
355
+
356
+ image_np_processed = image_np[y_min:y_max, x_min:x_max]
357
+ current_region_coords = (x_min, y_min, x_max, y_max)
358
+ print(f"DEBUG: Applied manual crop in segment_with_coords: {current_region_coords}, new image_np_processed shape: {image_np_processed.shape}")
359
+ else:
360
+ image_np_processed = image_np # No valid crop, use original
361
+ except ValueError:
362
+ print(f"DEBUG: Invalid crop coordinates format in segment_with_coords: {crop_coords}")
363
+ image_np_processed = image_np # Invalid crop, use original
364
+ else:
365
+ image_np_processed = image_np # No crop coords, use original
366
+
367
+ # Process image format
368
+ if len(image_np_processed.shape) == 2:
369
+ image_np_final = cv2.cvtColor(image_np_processed, cv2.COLOR_GRAY2RGB)
370
+ elif len(image_np_processed.shape) == 3 and image_np_processed.shape[2] == 4:
371
+ image_np_final = cv2.cvtColor(image_np_processed, cv2.COLOR_RGBA2RGB)
372
+ else:
373
+ image_np_final = image_np_processed
374
+
375
+ print(f"DEBUG: Image before model.eval (segment_with_coords) shape: {image_np_final.shape}, dtype: {image_np_final.dtype}")
376
+ # Run Cellpose
377
+ masks, flows, styles = model.eval(image_np_final, diameter=None, channels=[0, 0])
378
+ print(f"DEBUG: After model.eval (segment_with_coords): masks type: {type(masks)}, shape: {masks.shape if hasattr(masks, 'shape') else 'N/A'}")
379
+
380
+ # Count unique cells
381
+ cell_count = len(np.unique(masks)) - 1
382
+
383
+ # Create overlay
384
+ overlay = image_np_final.copy().astype(np.float32)
385
+
386
+ if masks.max() > 0:
387
+ np.random.seed(42)
388
+ colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
389
+ colors[0] = [0, 0, 0]
390
+ colored_mask = colors[masks]
391
+ alpha = 0.4
392
+ overlay = (1 - alpha) * overlay + alpha * colored_mask
393
+
394
+ overlay = np.clip(overlay, 0, 255).astype(np.uint8)
395
+ overlay_image = Image.fromarray(overlay)
396
+
397
+ info_msg = f"Segmentation complete! Found {cell_count} cells.\n"
398
+ if current_region_coords: # Use global if set by crop
399
+ info_msg += f"Region: {current_region_coords}\n"
400
+ info_msg += f"Now use the blue threshold slider below for viability assessment."
401
+ print("DEBUG: segment_with_coords returning successfully, viability section visible.")
402
+
403
+ return cell_count, overlay_image, info_msg, gr.update(visible=True), masks, image_np_final
404
+
405
+ except Exception as e:
406
+ print(f"ERROR: An exception occurred in segment_with_coords: {e}")
407
+ current_region_coords = None
408
+ return 0, None, f"Error: {str(e)}", gr.update(visible=False), None, None
409
+
410
+ # Create the Gradio interface
411
+ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=gr.themes.Soft()) as demo:
412
+ gr.Markdown("# Cell Counter with Real-time Viability Assessment")
413
+ gr.Markdown("Upload a microscopy image, run segmentation, then use the blue threshold slider for real-time viability assessment.")
414
+
415
+ # Define State components to store masks and image data across function calls
416
+ # These are hidden from the UI but persist data per session.
417
+ masks_state = gr.State(value=None)
418
+ image_state = gr.State(value=None)
419
+
420
+ with gr.Tab("Image Editor (Draw Selection)"):
421
+ gr.Markdown("### Step 1: Draw selection and run segmentation")
422
+
423
+ with gr.Row():
424
+ with gr.Column():
425
+ image_editor = gr.ImageEditor(
426
+ label="Draw selection on image",
427
+ type="pil",
428
+ brush=gr.Brush(colors=["#ff0000"], color_mode="fixed", default_size=20),
429
+ eraser=gr.Eraser(default_size=20)
430
+ )
431
+ model_dropdown1 = gr.Dropdown(
432
+ choices=list(MODEL_OPTIONS.keys()),
433
+ label="Select Model",
434
+ value="Hemocytometer Model"
435
+ )
436
+ segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
437
+
438
+ with gr.Column():
439
+ cell_count_output1 = gr.Number(label="Total Cells Detected", precision=0)
440
+ overlay_output1 = gr.Image(type="pil", label="Segmentation Result")
441
+ info_output1 = gr.Textbox(label="Processing Info", lines=3)
442
+
443
+ # Viability Assessment Section
444
+ with gr.Group(visible=False) as viability_section1:
445
+ gr.Markdown("### Step 2: Real-time Viability Assessment")
446
+ gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
447
+
448
+ with gr.Row():
449
+ with gr.Column():
450
+ blue_threshold_slider1 = gr.Slider(
451
+ minimum=0,
452
+ maximum=255,
453
+ value=100,
454
+ step=1,
455
+ label="Blue Threshold",
456
+ info="Higher values = more cells classified as dead"
457
+ )
458
+
459
+ with gr.Column():
460
+ live_count_output1 = gr.Number(label="Live Cells (Green)", precision=0)
461
+ dead_count_output1 = gr.Number(label="Dead Cells (Red)", precision=0)
462
+
463
+ viability_overlay1 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
464
+ viability_info1 = gr.Textbox(label="Viability Statistics", lines=4)
465
+
466
+ # Event handlers
467
+ # segment_cells now returns masks and image_np which are stored in masks_state and image_state
468
+ segment_btn1.click(
469
+ fn=segment_cells,
470
+ inputs=[image_editor, model_dropdown1],
471
+ outputs=[cell_count_output1, overlay_output1, info_output1, viability_section1, masks_state, image_state]
472
+ ).then( # Chain the viability assessment after segmentation
473
+ fn=update_viability_assessment,
474
+ inputs=[blue_threshold_slider1, masks_state, image_state], # Pass stored state as inputs
475
+ outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
476
+ )
477
+
478
+ # blue_threshold_slider changes also need the stored state
479
+ blue_threshold_slider1.change(
480
+ fn=update_viability_assessment,
481
+ inputs=[blue_threshold_slider1, masks_state, image_state], # Pass stored state as inputs
482
+ outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
483
+ )
484
+
485
+ with gr.Tab("Manual Coordinates"):
486
+ gr.Markdown("### Step 1: Upload image and run segmentation")
487
+
488
+ with gr.Row():
489
+ with gr.Column():
490
+ image_input = gr.Image(type="pil", label="Microscopy Image")
491
+ model_dropdown2 = gr.Dropdown(
492
+ choices=list(MODEL_OPTIONS.keys()),
493
+ label="Select Model",
494
+ value="Hemocytometer Model"
495
+ )
496
+ coord_input = gr.Textbox(
497
+ label="Crop Coordinates (optional)",
498
+ placeholder="e.g., 100,100,400,400",
499
+ info="Format: x_min,y_min,x_max,y_max"
500
+ )
501
+ segment_btn2 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
502
+
503
+ with gr.Column():
504
+ cell_count_output2 = gr.Number(label="Total Cells Detected", precision=0)
505
+ overlay_output2 = gr.Image(type="pil", label="Segmentation Result")
506
+ info_output2 = gr.Textbox(label="Processing Info", lines=3)
507
+
508
+ # Viability Assessment Section
509
+ with gr.Group(visible=False) as viability_section2:
510
+ gr.Markdown("### Step 2: Real-time Viability Assessment")
511
+ gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
512
+
513
+ with gr.Row():
514
+ with gr.Column():
515
+ blue_threshold_slider2 = gr.Slider(
516
+ minimum=0,
517
+ maximum=255,
518
+ value=100,
519
+ step=1,
520
+ label="Blue Threshold",
521
+ info="Higher values = more cells classified as dead"
522
+ )
523
+
524
+ with gr.Column():
525
+ live_count_output2 = gr.Number(label="Live Cells (Green)", precision=0)
526
+ dead_count_output2 = gr.Number(label="Dead Cells (Red)", precision=0)
527
+
528
+ viability_overlay2 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
529
+ viability_info2 = gr.Textbox(label="Viability Statistics", lines=4)
530
+
531
+ # Event handlers
532
+ # segment_with_coords now returns masks and image_np which are stored in masks_state and image_state
533
+ segment_btn2.click(
534
+ fn=segment_with_coords,
535
+ inputs=[image_input, model_dropdown2, coord_input],
536
+ outputs=[cell_count_output2, overlay_output2, info_output2, viability_section2, masks_state, image_state]
537
+ ).then( # Chain the viability assessment after segmentation
538
+ fn=update_viability_assessment,
539
+ inputs=[blue_threshold_slider2, masks_state, image_state], # Pass stored state as inputs
540
+ outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
541
+ )
542
+
543
+ # blue_threshold_slider changes also need the stored state
544
+ blue_threshold_slider2.change(
545
+ fn=update_viability_assessment,
546
+ inputs=[blue_threshold_slider2, masks_state, image_state], # Pass stored state as inputs
547
+ outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
548
+ )
549
+
550
+ # Instructions
551
+ with gr.Accordion("Instructions", open=False):
552
+ gr.Markdown("""
553
+ ### How to use:
554
+
555
+ 1. **Upload and Segment**:
556
+ - Upload your microscopy image
557
+ - Select a model (Hemocytometer Model for blood cells)
558
+ - Draw a selection region or specify coordinates
559
+ - Click "Run Segmentation"
560
+
561
+ 2. **Viability Assessment**:
562
+ - After segmentation, the viability section will appear
563
+ - Use the blue threshold slider to classify cells in real-time
564
+ - Green cells = Live (low blue intensity)
565
+ - Red cells = Dead (high blue intensity)
566
+ - Adjust the threshold until the classification looks correct
567
+
568
+ 3. **Interpreting Results**:
569
+ - The app calculates viability percentage automatically
570
+ - Live cells appear green, dead cells appear red
571
+ - Statistics show total counts and viability percentage
572
+
573
+ ### Tips:
574
+ - Start with a threshold around 100 and adjust based on your staining
575
+ - Dead cells typically have higher blue intensity due to trypan blue uptake
576
+ - The threshold is applied in real-time without re-running segmentation
577
+ """)
578
+
579
+ if __name__ == "__main__":
580
+ demo.launch()