LiangLabUMB commited on
Commit
5bd1ae5
·
verified ·
1 Parent(s): bccdaf4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -77
app.py CHANGED
@@ -14,7 +14,6 @@ MODEL_OPTIONS = {
14
  "Hemocytometer Model": "hemocytometermodel.npy",
15
  "General Model": "generalmodel.npy"
16
  }
17
-
18
  loaded_models = {}
19
 
20
  # Global variables to store segmentation results for real-time viability assessment
@@ -26,9 +25,7 @@ def debug_editor_data(editor_data):
26
  """Debug function to understand the structure of ImageEditor data"""
27
  if editor_data is None:
28
  return "No data provided"
29
-
30
  info = f"Type: {type(editor_data)}\n"
31
-
32
  if isinstance(editor_data, dict):
33
  info += f"Keys: {list(editor_data.keys())}\n"
34
  for key, value in editor_data.items():
@@ -40,30 +37,35 @@ def debug_editor_data(editor_data):
40
  if hasattr(layer, 'size'):
41
  info += f" Size: {layer.size}\n"
42
  info += f" Mode: {layer.mode}\n"
43
- else:
44
  if hasattr(editor_data, 'size'):
45
  info += f"Size: {editor_data.size}\n"
46
  info += f"Mode: {editor_data.mode}\n"
47
-
48
  return info
49
 
50
  def extract_region_from_editor(editor_data):
51
  """Extract the selected region from ImageEditor data"""
52
  if editor_data is None:
 
53
  return None, None
54
 
 
 
55
  if isinstance(editor_data, dict):
56
  background = editor_data.get('background')
57
  layers = editor_data.get('layers', [])
58
 
59
  if background is None:
 
60
  return None, None
61
-
62
  background_np = np.array(background)
 
63
 
64
  if layers and len(layers) > 0:
65
  selection_layer = layers[0]
66
  selection_np = np.array(selection_layer)
 
67
 
68
  if len(selection_np.shape) == 3:
69
  if selection_np.shape[2] == 4: # RGBA
@@ -86,15 +88,17 @@ def extract_region_from_editor(editor_data):
86
  x_max = min(w, x_max + pad)
87
 
88
  region = background_np[y_min:y_max+1, x_min:x_max+1]
 
89
  return region, (x_min, y_min, x_max, y_max)
90
-
91
  return background_np, None
92
-
93
- else:
94
  if hasattr(editor_data, 'size'):
95
  image_np = np.array(editor_data)
 
96
  return image_np, None
97
  else:
 
98
  return None, None
99
 
100
  def calculate_blue_intensity(image_np, masks):
@@ -106,7 +110,7 @@ def calculate_blue_intensity(image_np, masks):
106
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
107
  # Handle RGBA
108
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
109
-
110
  # Calculate blue intensity for each cell
111
  cell_blue_intensities = {}
112
  unique_cells = np.unique(masks)
@@ -118,7 +122,7 @@ def calculate_blue_intensity(image_np, masks):
118
  for cell_id in unique_cells:
119
  if cell_id == 0: # Skip background
120
  continue
121
-
122
  cell_mask = masks == cell_id
123
  cell_pixels = image_np[cell_mask]
124
 
@@ -126,8 +130,7 @@ def calculate_blue_intensity(image_np, masks):
126
  # Calculate mean blue intensity (blue channel is index 2)
127
  blue_intensity = np.mean(cell_pixels[:, 2]) # Blue channel
128
  cell_blue_intensities[cell_id] = blue_intensity
129
- print(f"DEBUG: Cell {cell_id}: {len(cell_pixels)} pixels, blue intensity: {blue_intensity:.2f}")
130
-
131
  print(f"DEBUG: Total cells with blue intensity: {len(cell_blue_intensities)}")
132
  return cell_blue_intensities
133
 
@@ -135,18 +138,18 @@ def create_viability_overlay(image_np, masks, blue_threshold):
135
  """Create overlay showing live/dead cells based on blue threshold"""
136
 
137
  if masks is None or image_np is None:
138
- print("DEBUG: masks or image_np is None")
139
  return None, 0, 0
140
 
141
  print(f"DEBUG: create_viability_overlay called with threshold: {blue_threshold}")
142
- print(f"DEBUG: Image shape: {image_np.shape}, Masks shape: {masks.shape}")
143
 
144
  # Ensure image is RGB
145
  if len(image_np.shape) == 2:
146
  image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
147
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
148
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
149
-
150
  # Calculate blue intensities
151
  cell_blue_intensities = calculate_blue_intensity(image_np, masks)
152
 
@@ -162,19 +165,18 @@ def create_viability_overlay(image_np, masks, blue_threshold):
162
  for cell_id, blue_intensity in cell_blue_intensities.items():
163
  cell_mask = masks == cell_id
164
 
165
- print(f"DEBUG: Processing cell {cell_id}, blue intensity: {blue_intensity:.2f}, threshold: {blue_threshold}")
166
 
167
  if blue_intensity > blue_threshold:
168
  # Dead cell - color in red
169
  overlay[cell_mask] = [255, 0, 0] # Red
170
  dead_count += 1
171
- print(f"DEBUG: Cell {cell_id} classified as DEAD")
172
  else:
173
  # Live cell - color in green
174
  overlay[cell_mask] = [0, 255, 0] # Green
175
  live_count += 1
176
- print(f"DEBUG: Cell {cell_id} classified as LIVE")
177
-
178
  print(f"DEBUG: Final counts - Live: {live_count}, Dead: {dead_count}")
179
 
180
  # Blend with original image
@@ -192,7 +194,9 @@ def segment_cells(editor_data, model_choice, crop_coords=None):
192
  global current_masks, current_image, current_region_coords
193
 
194
  try:
 
195
  debug_info = debug_editor_data(editor_data)
 
196
 
197
  # Load the model
198
  model_filename = MODEL_OPTIONS[model_choice]
@@ -208,9 +212,15 @@ def segment_cells(editor_data, model_choice, crop_coords=None):
208
  region_np, region_coords = extract_region_from_editor(editor_data)
209
 
210
  if region_np is None:
 
 
 
 
211
  return 0, None, f"No image provided. Debug info:\n{debug_info}", gr.update(visible=False)
212
 
213
- # Handle manual crop coordinates
 
 
214
  if crop_coords and crop_coords.strip():
215
  try:
216
  coords = [int(x.strip()) for x in crop_coords.split(',')]
@@ -221,10 +231,12 @@ def segment_cells(editor_data, model_choice, crop_coords=None):
221
  y_min = max(0, min(y_min, h-1))
222
  x_max = max(x_min+1, min(x_max, w))
223
  y_max = max(y_min+1, min(y_max, h))
224
-
225
  region_np = region_np[y_min:y_max, x_min:x_max]
226
- region_coords = (x_min, y_min, x_max, y_max)
 
227
  except ValueError:
 
228
  pass
229
 
230
  # Process image format
@@ -233,18 +245,24 @@ def segment_cells(editor_data, model_choice, crop_coords=None):
233
  elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
234
  region_np = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
235
 
 
236
  # Run Cellpose segmentation
237
  masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
 
238
 
239
  # Store results globally for viability assessment
240
  current_masks = masks
241
  current_image = region_np
242
  current_region_coords = region_coords
243
 
 
 
 
244
  # Count unique cells
245
  cell_count = len(np.unique(masks)) - 1
 
246
 
247
- # Create basic segmentation overlay
248
  overlay = region_np.copy().astype(np.float32)
249
 
250
  if masks.max() > 0:
@@ -255,7 +273,7 @@ def segment_cells(editor_data, model_choice, crop_coords=None):
255
  colored_mask = colors[masks]
256
  alpha = 0.4
257
  overlay = (1 - alpha) * overlay + alpha * colored_mask
258
-
259
  overlay = np.clip(overlay, 0, 255).astype(np.uint8)
260
  overlay_image = Image.fromarray(overlay)
261
 
@@ -266,10 +284,12 @@ def segment_cells(editor_data, model_choice, crop_coords=None):
266
 
267
  # Make viability section visible
268
  viability_section_update = gr.update(visible=True)
 
269
 
270
  return cell_count, overlay_image, info_msg, viability_section_update
271
 
272
  except Exception as e:
 
273
  current_masks = None
274
  current_image = None
275
  current_region_coords = None
@@ -281,7 +301,11 @@ def update_viability_assessment(blue_threshold):
281
  """
282
  global current_masks, current_image
283
 
 
 
 
284
  if current_masks is None or current_image is None:
 
285
  return None, 0, 0, "Please run segmentation first"
286
 
287
  try:
@@ -292,7 +316,7 @@ def update_viability_assessment(blue_threshold):
292
  debug_info = f"Debug - Total cells found: {total_cells}\n"
293
  debug_info += f"Image shape: {current_image.shape}\n"
294
  debug_info += f"Masks shape: {current_masks.shape}\n"
295
- debug_info += f"Unique mask values: {unique_cells[:10]}...\n" # Show first 10
296
 
297
  # Create viability overlay
298
  viability_overlay, live_count, dead_count = create_viability_overlay(
@@ -306,22 +330,32 @@ def update_viability_assessment(blue_threshold):
306
  if total_count > 0:
307
  viability_percentage = (live_count / total_count) * 100
308
  viability_info += f"Viability: {viability_percentage:.1f}%\n"
309
-
310
  viability_info += f"\n{debug_info}"
 
311
 
312
  return viability_overlay, live_count, dead_count, viability_info
313
 
314
  except Exception as e:
 
315
  return None, 0, 0, f"Error in viability assessment: {str(e)}\nMasks available: {current_masks is not None}\nImage available: {current_image is not None}"
316
 
 
 
 
317
  @spaces.GPU
318
  def segment_with_coords(image, model_choice, crop_coords):
319
  """Alternative function for manual coordinates input"""
320
  global current_masks, current_image, current_region_coords
321
-
 
322
  if image is None:
 
 
 
 
323
  return 0, None, "No image provided", gr.update(visible=False)
324
-
325
  try:
326
  # Load the model
327
  model_filename = MODEL_OPTIONS[model_choice]
@@ -332,10 +366,11 @@ def segment_with_coords(image, model_choice, crop_coords):
332
  else:
333
  model = models.CellposeModel(gpu=True, pretrained_model=model_path)
334
  loaded_models[model_filename] = model
335
-
336
  # Convert PIL Image to numpy array
337
  image_np = np.array(image)
338
-
 
339
  # Apply crop if coordinates are provided
340
  if crop_coords and crop_coords.strip():
341
  try:
@@ -347,51 +382,61 @@ def segment_with_coords(image, model_choice, crop_coords):
347
  y_min = max(0, min(y_min, h-1))
348
  x_max = max(x_min+1, min(x_max, w))
349
  y_max = max(y_min+1, min(y_max, h))
350
-
351
  image_np = image_np[y_min:y_max, x_min:x_max]
352
  current_region_coords = (x_min, y_min, x_max, y_max)
 
353
  except ValueError:
 
354
  pass
355
-
356
  # Process image format
357
  if len(image_np.shape) == 2:
358
  image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
359
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
360
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
361
 
 
362
  # Run Cellpose
363
  masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
364
-
 
365
  # Store results globally
366
  current_masks = masks
367
  current_image = image_np
368
 
 
 
 
369
  # Count unique cells
370
  cell_count = len(np.unique(masks)) - 1
371
-
372
  # Create overlay
373
  overlay = image_np.copy().astype(np.float32)
374
-
375
  if masks.max() > 0:
376
  np.random.seed(42)
377
  colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
378
- colors[0] = [0, 0, 0]
379
-
380
  colored_mask = colors[masks]
381
  alpha = 0.4
382
  overlay = (1 - alpha) * overlay + alpha * colored_mask
383
-
384
  overlay = np.clip(overlay, 0, 255).astype(np.uint8)
385
  overlay_image = Image.fromarray(overlay)
386
-
387
  info_msg = f"Segmentation complete! Found {cell_count} cells.\n"
388
  info_msg += f"Now use the blue threshold slider below for viability assessment."
389
-
 
390
  return cell_count, overlay_image, info_msg, gr.update(visible=True)
391
-
392
  except Exception as e:
 
393
  current_masks = None
394
  current_image = None
 
395
  return 0, None, f"Error: {str(e)}", gr.update(visible=False)
396
 
397
  # Create the Gradio interface
@@ -416,17 +461,17 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
416
  value="Hemocytometer Model"
417
  )
418
  segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
419
-
420
  with gr.Column():
421
  cell_count_output1 = gr.Number(label="Total Cells Detected", precision=0)
422
  overlay_output1 = gr.Image(type="pil", label="Segmentation Result")
423
  info_output1 = gr.Textbox(label="Processing Info", lines=3)
424
-
425
  # Viability Assessment Section
426
  with gr.Group(visible=False) as viability_section1:
427
  gr.Markdown("### Step 2: Real-time Viability Assessment")
428
  gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
429
-
430
  with gr.Row():
431
  with gr.Column():
432
  blue_threshold_slider1 = gr.Slider(
@@ -437,14 +482,14 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
437
  label="Blue Threshold",
438
  info="Higher values = more cells classified as dead"
439
  )
440
-
441
  with gr.Column():
442
  live_count_output1 = gr.Number(label="Live Cells (Green)", precision=0)
443
  dead_count_output1 = gr.Number(label="Dead Cells (Red)", precision=0)
444
-
445
  viability_overlay1 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
446
  viability_info1 = gr.Textbox(label="Viability Statistics", lines=4)
447
-
448
  # Event handlers
449
  segment_btn1.click(
450
  fn=segment_cells,
@@ -455,16 +500,16 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
455
  inputs=[blue_threshold_slider1],
456
  outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
457
  )
458
-
459
  blue_threshold_slider1.change(
460
  fn=update_viability_assessment,
461
  inputs=[blue_threshold_slider1],
462
  outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
463
  )
464
-
465
  with gr.Tab("Manual Coordinates"):
466
  gr.Markdown("### Step 1: Upload image and run segmentation")
467
-
468
  with gr.Row():
469
  with gr.Column():
470
  image_input = gr.Image(type="pil", label="Microscopy Image")
@@ -479,17 +524,17 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
479
  info="Format: x_min,y_min,x_max,y_max"
480
  )
481
  segment_btn2 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
482
-
483
  with gr.Column():
484
  cell_count_output2 = gr.Number(label="Total Cells Detected", precision=0)
485
  overlay_output2 = gr.Image(type="pil", label="Segmentation Result")
486
  info_output2 = gr.Textbox(label="Processing Info", lines=3)
487
-
488
  # Viability Assessment Section
489
  with gr.Group(visible=False) as viability_section2:
490
  gr.Markdown("### Step 2: Real-time Viability Assessment")
491
  gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
492
-
493
  with gr.Row():
494
  with gr.Column():
495
  blue_threshold_slider2 = gr.Slider(
@@ -500,14 +545,14 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
500
  label="Blue Threshold",
501
  info="Higher values = more cells classified as dead"
502
  )
503
-
504
  with gr.Column():
505
  live_count_output2 = gr.Number(label="Live Cells (Green)", precision=0)
506
  dead_count_output2 = gr.Number(label="Dead Cells (Red)", precision=0)
507
-
508
  viability_overlay2 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
509
  viability_info2 = gr.Textbox(label="Viability Statistics", lines=4)
510
-
511
  # Event handlers
512
  segment_btn2.click(
513
  fn=segment_with_coords,
@@ -518,36 +563,36 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
518
  inputs=[blue_threshold_slider2],
519
  outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
520
  )
521
-
522
  blue_threshold_slider2.change(
523
  fn=update_viability_assessment,
524
  inputs=[blue_threshold_slider2],
525
  outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
526
  )
527
-
528
  # Instructions
529
  with gr.Accordion("Instructions", open=False):
530
  gr.Markdown("""
531
  ### How to use:
532
-
533
- 1. **Upload and Segment**:
534
- - Upload your microscopy image
535
- - Select a model (Hemocytometer Model for blood cells)
536
- - Draw a selection region or specify coordinates
537
- - Click "Run Segmentation"
538
-
539
  2. **Viability Assessment**:
540
- - After segmentation, the viability section will appear
541
- - Use the blue threshold slider to classify cells in real-time
542
- - Green cells = Live (low blue intensity)
543
- - Red cells = Dead (high blue intensity)
544
- - Adjust the threshold until the classification looks correct
545
-
546
  3. **Interpreting Results**:
547
- - The app calculates viability percentage automatically
548
- - Live cells appear green, dead cells appear red
549
- - Statistics show total counts and viability percentage
550
-
551
  ### Tips:
552
  - Start with a threshold around 100 and adjust based on your staining
553
  - Dead cells typically have higher blue intensity due to trypan blue uptake
@@ -555,4 +600,4 @@ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=g
555
  """)
556
 
557
  if __name__ == "__main__":
558
- demo.launch()
 
14
  "Hemocytometer Model": "hemocytometermodel.npy",
15
  "General Model": "generalmodel.npy"
16
  }
 
17
  loaded_models = {}
18
 
19
  # Global variables to store segmentation results for real-time viability assessment
 
25
  """Debug function to understand the structure of ImageEditor data"""
26
  if editor_data is None:
27
  return "No data provided"
 
28
  info = f"Type: {type(editor_data)}\n"
 
29
  if isinstance(editor_data, dict):
30
  info += f"Keys: {list(editor_data.keys())}\n"
31
  for key, value in editor_data.items():
 
37
  if hasattr(layer, 'size'):
38
  info += f" Size: {layer.size}\n"
39
  info += f" Mode: {layer.mode}\n"
40
+ else: # Direct PIL Image
41
  if hasattr(editor_data, 'size'):
42
  info += f"Size: {editor_data.size}\n"
43
  info += f"Mode: {editor_data.mode}\n"
 
44
  return info
45
 
46
  def extract_region_from_editor(editor_data):
47
  """Extract the selected region from ImageEditor data"""
48
  if editor_data is None:
49
+ print("DEBUG: extract_region_from_editor - editor_data is None")
50
  return None, None
51
 
52
+ print(f"DEBUG: extract_region_from_editor - editor_data type: {type(editor_data)}")
53
+
54
  if isinstance(editor_data, dict):
55
  background = editor_data.get('background')
56
  layers = editor_data.get('layers', [])
57
 
58
  if background is None:
59
+ print("DEBUG: extract_region_from_editor - background is None")
60
  return None, None
61
+
62
  background_np = np.array(background)
63
+ print(f"DEBUG: extract_region_from_editor - background_np shape: {background_np.shape}")
64
 
65
  if layers and len(layers) > 0:
66
  selection_layer = layers[0]
67
  selection_np = np.array(selection_layer)
68
+ print(f"DEBUG: extract_region_from_editor - selection_np shape: {selection_np.shape}")
69
 
70
  if len(selection_np.shape) == 3:
71
  if selection_np.shape[2] == 4: # RGBA
 
88
  x_max = min(w, x_max + pad)
89
 
90
  region = background_np[y_min:y_max+1, x_min:x_max+1]
91
+ print(f"DEBUG: extract_region_from_editor - Extracted region shape: {region.shape}")
92
  return region, (x_min, y_min, x_max, y_max)
93
+ print("DEBUG: extract_region_from_editor - No selection or empty selection, returning full background.")
94
  return background_np, None
95
+ else: # Assuming it's a direct PIL Image if not dict
 
96
  if hasattr(editor_data, 'size'):
97
  image_np = np.array(editor_data)
98
+ print(f"DEBUG: extract_region_from_editor - Direct PIL image, shape: {image_np.shape}")
99
  return image_np, None
100
  else:
101
+ print("DEBUG: extract_region_from_editor - editor_data is not dict and no size attribute, returning None.")
102
  return None, None
103
 
104
  def calculate_blue_intensity(image_np, masks):
 
110
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
111
  # Handle RGBA
112
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
113
+
114
  # Calculate blue intensity for each cell
115
  cell_blue_intensities = {}
116
  unique_cells = np.unique(masks)
 
122
  for cell_id in unique_cells:
123
  if cell_id == 0: # Skip background
124
  continue
125
+
126
  cell_mask = masks == cell_id
127
  cell_pixels = image_np[cell_mask]
128
 
 
130
  # Calculate mean blue intensity (blue channel is index 2)
131
  blue_intensity = np.mean(cell_pixels[:, 2]) # Blue channel
132
  cell_blue_intensities[cell_id] = blue_intensity
133
+ # print(f"DEBUG: Cell {cell_id}: {len(cell_pixels)} pixels, blue intensity: {blue_intensity:.2f}") # Too verbose, uncomment if needed
 
134
  print(f"DEBUG: Total cells with blue intensity: {len(cell_blue_intensities)}")
135
  return cell_blue_intensities
136
 
 
138
  """Create overlay showing live/dead cells based on blue threshold"""
139
 
140
  if masks is None or image_np is None:
141
+ print("DEBUG: create_viability_overlay - masks or image_np is None, returning None.")
142
  return None, 0, 0
143
 
144
  print(f"DEBUG: create_viability_overlay called with threshold: {blue_threshold}")
145
+ print(f"DEBUG: Image shape: {image_np.shape}, Masks shape: {masks.shape}, Masks max: {masks.max()}")
146
 
147
  # Ensure image is RGB
148
  if len(image_np.shape) == 2:
149
  image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
150
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
151
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
152
+
153
  # Calculate blue intensities
154
  cell_blue_intensities = calculate_blue_intensity(image_np, masks)
155
 
 
165
  for cell_id, blue_intensity in cell_blue_intensities.items():
166
  cell_mask = masks == cell_id
167
 
168
+ # print(f"DEBUG: Processing cell {cell_id}, blue intensity: {blue_intensity:.2f}, threshold: {blue_threshold}") # Too verbose
169
 
170
  if blue_intensity > blue_threshold:
171
  # Dead cell - color in red
172
  overlay[cell_mask] = [255, 0, 0] # Red
173
  dead_count += 1
174
+ # print(f"DEBUG: Cell {cell_id} classified as DEAD") # Too verbose
175
  else:
176
  # Live cell - color in green
177
  overlay[cell_mask] = [0, 255, 0] # Green
178
  live_count += 1
179
+ # print(f"DEBUG: Cell {cell_id} classified as LIVE") # Too verbose
 
180
  print(f"DEBUG: Final counts - Live: {live_count}, Dead: {dead_count}")
181
 
182
  # Blend with original image
 
194
  global current_masks, current_image, current_region_coords
195
 
196
  try:
197
+ print("DEBUG: --- Starting segment_cells ---")
198
  debug_info = debug_editor_data(editor_data)
199
+ print(f"DEBUG: Editor data info:\n{debug_info}")
200
 
201
  # Load the model
202
  model_filename = MODEL_OPTIONS[model_choice]
 
212
  region_np, region_coords = extract_region_from_editor(editor_data)
213
 
214
  if region_np is None:
215
+ print("DEBUG: region_np is None, returning error and hiding viability section.")
216
+ current_masks = None # Ensure globals are reset
217
+ current_image = None
218
+ current_region_coords = None
219
  return 0, None, f"No image provided. Debug info:\n{debug_info}", gr.update(visible=False)
220
 
221
+ print(f"DEBUG: Image to segment shape (after extraction/cropping): {region_np.shape}, dtype: {region_np.dtype}")
222
+
223
+ # Handle manual crop coordinates (logic already exists, no extra prints needed here unless it's the issue)
224
  if crop_coords and crop_coords.strip():
225
  try:
226
  coords = [int(x.strip()) for x in crop_coords.split(',')]
 
231
  y_min = max(0, min(y_min, h-1))
232
  x_max = max(x_min+1, min(x_max, w))
233
  y_max = max(y_min+1, min(y_max, h))
234
+
235
  region_np = region_np[y_min:y_max, x_min:x_max]
236
+ current_region_coords = (x_min, y_min, x_max, y_max)
237
+ print(f"DEBUG: Applied manual crop: {current_region_coords}, new region shape: {region_np.shape}")
238
  except ValueError:
239
+ print(f"DEBUG: Invalid crop coordinates format: {crop_coords}")
240
  pass
241
 
242
  # Process image format
 
245
  elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
246
  region_np = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
247
 
248
+ print(f"DEBUG: Image before model.eval shape: {region_np.shape}, dtype: {region_np.dtype}")
249
  # Run Cellpose segmentation
250
  masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
251
+ print(f"DEBUG: After model.eval: masks type: {type(masks)}, shape: {masks.shape if hasattr(masks, 'shape') else 'N/A'}")
252
 
253
  # Store results globally for viability assessment
254
  current_masks = masks
255
  current_image = region_np
256
  current_region_coords = region_coords
257
 
258
+ print(f"DEBUG: Globals set - current_masks is None? {current_masks is None}, current_image is None? {current_image is None}")
259
+ print(f"DEBUG: Globals set - current_masks shape: {current_masks.shape if current_masks is not None else 'N/A'}")
260
+
261
  # Count unique cells
262
  cell_count = len(np.unique(masks)) - 1
263
+ print(f"DEBUG: Cell count: {cell_count}")
264
 
265
+ # Create basic segmentation overlay (logic unchanged)
266
  overlay = region_np.copy().astype(np.float32)
267
 
268
  if masks.max() > 0:
 
273
  colored_mask = colors[masks]
274
  alpha = 0.4
275
  overlay = (1 - alpha) * overlay + alpha * colored_mask
276
+
277
  overlay = np.clip(overlay, 0, 255).astype(np.uint8)
278
  overlay_image = Image.fromarray(overlay)
279
 
 
284
 
285
  # Make viability section visible
286
  viability_section_update = gr.update(visible=True)
287
+ print("DEBUG: segment_cells returning successfully, viability section visible.")
288
 
289
  return cell_count, overlay_image, info_msg, viability_section_update
290
 
291
  except Exception as e:
292
+ print(f"ERROR: An exception occurred in segment_cells: {e}")
293
  current_masks = None
294
  current_image = None
295
  current_region_coords = None
 
301
  """
302
  global current_masks, current_image
303
 
304
+ print("DEBUG: --- Starting update_viability_assessment ---")
305
+ print(f"DEBUG: At start of update_viability_assessment: current_masks is None? {current_masks is None}, current_image is None? {current_image is None}")
306
+
307
  if current_masks is None or current_image is None:
308
+ print("DEBUG: current_masks or current_image is None, returning 'Please run segmentation first'.")
309
  return None, 0, 0, "Please run segmentation first"
310
 
311
  try:
 
316
  debug_info = f"Debug - Total cells found: {total_cells}\n"
317
  debug_info += f"Image shape: {current_image.shape}\n"
318
  debug_info += f"Masks shape: {current_masks.shape}\n"
319
+ debug_info += f"Unique mask values (first 10): {unique_cells[:10]}...\n"
320
 
321
  # Create viability overlay
322
  viability_overlay, live_count, dead_count = create_viability_overlay(
 
330
  if total_count > 0:
331
  viability_percentage = (live_count / total_count) * 100
332
  viability_info += f"Viability: {viability_percentage:.1f}%\n"
333
+
334
  viability_info += f"\n{debug_info}"
335
+ print("DEBUG: update_viability_assessment returning successfully.")
336
 
337
  return viability_overlay, live_count, dead_count, viability_info
338
 
339
  except Exception as e:
340
+ print(f"ERROR: An exception occurred in update_viability_assessment: {e}")
341
  return None, 0, 0, f"Error in viability assessment: {str(e)}\nMasks available: {current_masks is not None}\nImage available: {current_image is not None}"
342
 
343
+ # The segment_with_coords function and Gradio UI setup remain the same
344
+ # ... (rest of your app.py code) ...
345
+
346
  @spaces.GPU
347
  def segment_with_coords(image, model_choice, crop_coords):
348
  """Alternative function for manual coordinates input"""
349
  global current_masks, current_image, current_region_coords
350
+
351
+ print("DEBUG: --- Starting segment_with_coords ---")
352
  if image is None:
353
+ print("DEBUG: No image provided for segment_with_coords, returning error.")
354
+ current_masks = None
355
+ current_image = None
356
+ current_region_coords = None
357
  return 0, None, "No image provided", gr.update(visible=False)
358
+
359
  try:
360
  # Load the model
361
  model_filename = MODEL_OPTIONS[model_choice]
 
366
  else:
367
  model = models.CellposeModel(gpu=True, pretrained_model=model_path)
368
  loaded_models[model_filename] = model
369
+
370
  # Convert PIL Image to numpy array
371
  image_np = np.array(image)
372
+ print(f"DEBUG: image_np from PIL: shape={image_np.shape}, dtype={image_np.dtype}")
373
+
374
  # Apply crop if coordinates are provided
375
  if crop_coords and crop_coords.strip():
376
  try:
 
382
  y_min = max(0, min(y_min, h-1))
383
  x_max = max(x_min+1, min(x_max, w))
384
  y_max = max(y_min+1, min(y_max, h))
385
+
386
  image_np = image_np[y_min:y_max, x_min:x_max]
387
  current_region_coords = (x_min, y_min, x_max, y_max)
388
+ print(f"DEBUG: Applied manual crop in segment_with_coords: {current_region_coords}, new image_np shape: {image_np.shape}")
389
  except ValueError:
390
+ print(f"DEBUG: Invalid crop coordinates format in segment_with_coords: {crop_coords}")
391
  pass
392
+
393
  # Process image format
394
  if len(image_np.shape) == 2:
395
  image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
396
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
397
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
398
 
399
+ print(f"DEBUG: Image before model.eval (segment_with_coords) shape: {image_np.shape}, dtype: {image_np.dtype}")
400
  # Run Cellpose
401
  masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
402
+ print(f"DEBUG: After model.eval (segment_with_coords): masks type: {type(masks)}, shape: {masks.shape if hasattr(masks, 'shape') else 'N/A'}")
403
+
404
  # Store results globally
405
  current_masks = masks
406
  current_image = image_np
407
 
408
+ print(f"DEBUG: Globals set (segment_with_coords) - current_masks is None? {current_masks is None}, current_image is None? {current_image is None}")
409
+ print(f"DEBUG: Globals set (segment_with_coords) - current_masks shape: {current_masks.shape if current_masks is not None else 'N/A'}")
410
+
411
  # Count unique cells
412
  cell_count = len(np.unique(masks)) - 1
413
+
414
  # Create overlay
415
  overlay = image_np.copy().astype(np.float32)
416
+
417
  if masks.max() > 0:
418
  np.random.seed(42)
419
  colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
420
+ colors[0] = [0, 0, 0]
421
+
422
  colored_mask = colors[masks]
423
  alpha = 0.4
424
  overlay = (1 - alpha) * overlay + alpha * colored_mask
425
+
426
  overlay = np.clip(overlay, 0, 255).astype(np.uint8)
427
  overlay_image = Image.fromarray(overlay)
428
+
429
  info_msg = f"Segmentation complete! Found {cell_count} cells.\n"
430
  info_msg += f"Now use the blue threshold slider below for viability assessment."
431
+ print("DEBUG: segment_with_coords returning successfully, viability section visible.")
432
+
433
  return cell_count, overlay_image, info_msg, gr.update(visible=True)
434
+
435
  except Exception as e:
436
+ print(f"ERROR: An exception occurred in segment_with_coords: {e}")
437
  current_masks = None
438
  current_image = None
439
+ current_region_coords = None
440
  return 0, None, f"Error: {str(e)}", gr.update(visible=False)
441
 
442
  # Create the Gradio interface
 
461
  value="Hemocytometer Model"
462
  )
463
  segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
464
+
465
  with gr.Column():
466
  cell_count_output1 = gr.Number(label="Total Cells Detected", precision=0)
467
  overlay_output1 = gr.Image(type="pil", label="Segmentation Result")
468
  info_output1 = gr.Textbox(label="Processing Info", lines=3)
469
+
470
  # Viability Assessment Section
471
  with gr.Group(visible=False) as viability_section1:
472
  gr.Markdown("### Step 2: Real-time Viability Assessment")
473
  gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
474
+
475
  with gr.Row():
476
  with gr.Column():
477
  blue_threshold_slider1 = gr.Slider(
 
482
  label="Blue Threshold",
483
  info="Higher values = more cells classified as dead"
484
  )
485
+
486
  with gr.Column():
487
  live_count_output1 = gr.Number(label="Live Cells (Green)", precision=0)
488
  dead_count_output1 = gr.Number(label="Dead Cells (Red)", precision=0)
489
+
490
  viability_overlay1 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
491
  viability_info1 = gr.Textbox(label="Viability Statistics", lines=4)
492
+
493
  # Event handlers
494
  segment_btn1.click(
495
  fn=segment_cells,
 
500
  inputs=[blue_threshold_slider1],
501
  outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
502
  )
503
+
504
  blue_threshold_slider1.change(
505
  fn=update_viability_assessment,
506
  inputs=[blue_threshold_slider1],
507
  outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
508
  )
509
+
510
  with gr.Tab("Manual Coordinates"):
511
  gr.Markdown("### Step 1: Upload image and run segmentation")
512
+
513
  with gr.Row():
514
  with gr.Column():
515
  image_input = gr.Image(type="pil", label="Microscopy Image")
 
524
  info="Format: x_min,y_min,x_max,y_max"
525
  )
526
  segment_btn2 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
527
+
528
  with gr.Column():
529
  cell_count_output2 = gr.Number(label="Total Cells Detected", precision=0)
530
  overlay_output2 = gr.Image(type="pil", label="Segmentation Result")
531
  info_output2 = gr.Textbox(label="Processing Info", lines=3)
532
+
533
  # Viability Assessment Section
534
  with gr.Group(visible=False) as viability_section2:
535
  gr.Markdown("### Step 2: Real-time Viability Assessment")
536
  gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
537
+
538
  with gr.Row():
539
  with gr.Column():
540
  blue_threshold_slider2 = gr.Slider(
 
545
  label="Blue Threshold",
546
  info="Higher values = more cells classified as dead"
547
  )
548
+
549
  with gr.Column():
550
  live_count_output2 = gr.Number(label="Live Cells (Green)", precision=0)
551
  dead_count_output2 = gr.Number(label="Dead Cells (Red)", precision=0)
552
+
553
  viability_overlay2 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
554
  viability_info2 = gr.Textbox(label="Viability Statistics", lines=4)
555
+
556
  # Event handlers
557
  segment_btn2.click(
558
  fn=segment_with_coords,
 
563
  inputs=[blue_threshold_slider2],
564
  outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
565
  )
566
+
567
  blue_threshold_slider2.change(
568
  fn=update_viability_assessment,
569
  inputs=[blue_threshold_slider2],
570
  outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
571
  )
572
+
573
  # Instructions
574
  with gr.Accordion("Instructions", open=False):
575
  gr.Markdown("""
576
  ### How to use:
577
+
578
+ 1. **Upload and Segment**:
579
+ - Upload your microscopy image
580
+ - Select a model (Hemocytometer Model for blood cells)
581
+ - Draw a selection region or specify coordinates
582
+ - Click "Run Segmentation"
583
+
584
  2. **Viability Assessment**:
585
+ - After segmentation, the viability section will appear
586
+ - Use the blue threshold slider to classify cells in real-time
587
+ - Green cells = Live (low blue intensity)
588
+ - Red cells = Dead (high blue intensity)
589
+ - Adjust the threshold until the classification looks correct
590
+
591
  3. **Interpreting Results**:
592
+ - The app calculates viability percentage automatically
593
+ - Live cells appear green, dead cells appear red
594
+ - Statistics show total counts and viability percentage
595
+
596
  ### Tips:
597
  - Start with a threshold around 100 and adjust based on your staining
598
  - Dead cells typically have higher blue intensity due to trypan blue uptake
 
600
  """)
601
 
602
  if __name__ == "__main__":
603
+ demo.launch()