LiangLabUMB commited on
Commit
965469b
·
verified ·
1 Parent(s): 19e1cd7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -59
app.py CHANGED
@@ -119,16 +119,14 @@ def crop_image_with_coords(image_np, coords):
119
  x_min, y_min, x_max, y_max = coords
120
  return image_np[y_min:y_max+1, x_min:x_max+1]
121
 
122
- def classify_cells_by_color(image_np, masks, hue_min, hue_max, sat_min, val_min, val_max):
123
  """
124
- Classify cells as dead (blue) or alive based on trypan blue color threshold
125
 
126
  Args:
127
  image_np: RGB image array
128
  masks: Cellpose segmentation masks
129
- hue_min, hue_max: Hue range for blue color (0-179 in OpenCV)
130
- sat_min: Minimum saturation (0-255)
131
- val_min, val_max: Value (brightness) range (0-255)
132
 
133
  Returns:
134
  dead_count, alive_count, colored_overlay
@@ -136,14 +134,20 @@ def classify_cells_by_color(image_np, masks, hue_min, hue_max, sat_min, val_min,
136
  # Convert RGB to HSV
137
  hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)
138
 
139
- # Create color mask for trypan blue
140
- if hue_min <= hue_max:
141
- color_mask = cv2.inRange(hsv, (hue_min, sat_min, val_min), (hue_max, 255, val_max))
142
- else:
143
- # Handle hue wrap-around (e.g., 170-10 for red-blue boundary)
144
- mask1 = cv2.inRange(hsv, (hue_min, sat_min, val_min), (179, 255, val_max))
145
- mask2 = cv2.inRange(hsv, (0, sat_min, val_min), (hue_max, 255, val_max))
146
- color_mask = cv2.bitwise_or(mask1, mask2)
 
 
 
 
 
 
147
 
148
  # Get unique cell IDs (excluding background)
149
  cell_ids = np.unique(masks)
@@ -157,12 +161,11 @@ def classify_cells_by_color(image_np, masks, hue_min, hue_max, sat_min, val_min,
157
  # Get mask for this specific cell
158
  cell_mask = (masks == cell_id)
159
 
160
- # Calculate overlap with color mask
161
- overlap = np.sum(color_mask[cell_mask] > 0)
162
- total_pixels = np.sum(cell_mask)
163
 
164
- # If >30% of cell pixels are blue, classify as dead
165
- if overlap / total_pixels > 0.3:
166
  dead_cells.append(cell_id)
167
  else:
168
  alive_cells.append(cell_id)
@@ -187,9 +190,9 @@ def classify_cells_by_color(image_np, masks, hue_min, hue_max, sat_min, val_min,
187
  return len(dead_cells), len(alive_cells), overlay
188
 
189
  @spaces.GPU
190
- def segment_and_count_viability(editor_data, model_choice, hue_min, hue_max, sat_min, val_min, val_max, crop_coords=None):
191
  """
192
- Segment cells and analyze viability using trypan blue color threshold
193
  """
194
  try:
195
  # Debug info
@@ -239,9 +242,9 @@ def segment_and_count_viability(editor_data, model_choice, hue_min, hue_max, sat
239
  # Run Cellpose on the selected region
240
  masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
241
 
242
- # Classify cells by color
243
- dead_count, alive_count, viability_overlay = classify_cells_by_color(
244
- region_np, masks, hue_min, hue_max, sat_min, val_min, val_max
245
  )
246
 
247
  total_count = dead_count + alive_count
@@ -251,7 +254,7 @@ def segment_and_count_viability(editor_data, model_choice, hue_min, hue_max, sat
251
  overlay_image = Image.fromarray(viability_overlay)
252
 
253
  # Create info message
254
- info_msg = f"Total cells: {total_count}\nAlive cells: {alive_count}\nDead cells: {dead_count}\nViability: {viability_percent:.1f}%"
255
  if region_coords:
256
  info_msg += f"\nProcessed region: {region_coords[0]},{region_coords[1]} to {region_coords[2]},{region_coords[3]}"
257
 
@@ -261,7 +264,7 @@ def segment_and_count_viability(editor_data, model_choice, hue_min, hue_max, sat
261
  return 0, 0, 0, 0.0, None, f"Error occurred: {str(e)}\nDebug info:\n{debug_info if 'debug_info' in locals() else 'No debug info'}"
262
 
263
  @spaces.GPU
264
- def segment_with_coords_viability(image, model_choice, hue_min, hue_max, sat_min, val_min, val_max, crop_coords):
265
  """Segment using regular image input with manual coordinate specification and viability analysis"""
266
  if image is None:
267
  return 0, 0, 0, 0.0, None, "No image provided"
@@ -306,9 +309,9 @@ def segment_with_coords_viability(image, model_choice, hue_min, hue_max, sat_min
306
  # Run Cellpose
307
  masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
308
 
309
- # Classify cells by color
310
- dead_count, alive_count, viability_overlay = classify_cells_by_color(
311
- image_np, masks, hue_min, hue_max, sat_min, val_min, val_max
312
  )
313
 
314
  total_count = dead_count + alive_count
@@ -316,7 +319,7 @@ def segment_with_coords_viability(image, model_choice, hue_min, hue_max, sat_min
316
 
317
  overlay_image = Image.fromarray(viability_overlay)
318
 
319
- info_msg = f"Total cells: {total_count}\nAlive cells: {alive_count}\nDead cells: {dead_count}\nViability: {viability_percent:.1f}%"
320
  if crop_coords:
321
  info_msg += f"\nProcessed with coordinates: {crop_coords}"
322
 
@@ -348,16 +351,13 @@ with gr.Blocks(title="Cell Viability Counter with Trypan Blue Analysis") as demo
348
  value="Hemocytometer Model"
349
  )
350
 
351
- # Color threshold controls
352
- gr.Markdown("### Trypan Blue Color Threshold")
353
- with gr.Row():
354
- hue_min1 = gr.Slider(0, 179, value=100, step=1, label="Hue Min (Blue)")
355
- hue_max1 = gr.Slider(0, 179, value=130, step=1, label="Hue Max (Blue)")
356
-
357
- with gr.Row():
358
- sat_min1 = gr.Slider(0, 255, value=50, step=1, label="Saturation Min")
359
- val_min1 = gr.Slider(0, 255, value=50, step=1, label="Value Min")
360
- val_max1 = gr.Slider(0, 255, value=255, step=1, label="Value Max")
361
 
362
  segment_btn1 = gr.Button("Analyze Viability", variant="primary")
363
 
@@ -373,7 +373,7 @@ with gr.Blocks(title="Cell Viability Counter with Trypan Blue Analysis") as demo
373
 
374
  segment_btn1.click(
375
  fn=segment_and_count_viability,
376
- inputs=[image_editor, model_dropdown1, hue_min1, hue_max1, sat_min1, val_min1, val_max1],
377
  outputs=[total_count_output1, alive_count_output1, dead_count_output1, viability_output1, overlay_output1, info_output1]
378
  )
379
 
@@ -396,16 +396,13 @@ with gr.Blocks(title="Cell Viability Counter with Trypan Blue Analysis") as demo
396
  info="Format: x_min,y_min,x_max,y_max"
397
  )
398
 
399
- # Color threshold controls
400
- gr.Markdown("### Trypan Blue Color Threshold")
401
- with gr.Row():
402
- hue_min2 = gr.Slider(0, 179, value=100, step=1, label="Hue Min (Blue)")
403
- hue_max2 = gr.Slider(0, 179, value=130, step=1, label="Hue Max (Blue)")
404
-
405
- with gr.Row():
406
- sat_min2 = gr.Slider(0, 255, value=50, step=1, label="Saturation Min")
407
- val_min2 = gr.Slider(0, 255, value=50, step=1, label="Value Min")
408
- val_max2 = gr.Slider(0, 255, value=255, step=1, label="Value Max")
409
 
410
  segment_btn2 = gr.Button("Analyze Viability", variant="primary")
411
 
@@ -421,7 +418,7 @@ with gr.Blocks(title="Cell Viability Counter with Trypan Blue Analysis") as demo
421
 
422
  segment_btn2.click(
423
  fn=segment_with_coords_viability,
424
- inputs=[image_input, model_dropdown2, hue_min2, hue_max2, sat_min2, val_min2, val_max2, coord_input],
425
  outputs=[total_count_output2, alive_count_output2, dead_count_output2, viability_output2, overlay_output2, info_output2]
426
  )
427
 
@@ -434,14 +431,17 @@ with gr.Blocks(title="Cell Viability Counter with Trypan Blue Analysis") as demo
434
  - Upload a trypan blue stained microscopy image
435
  - Dead cells should appear blue, alive cells should be unstained (clear)
436
 
437
- ### 2. Color Threshold Adjustment
438
- - **Hue Min/Max**: Adjust to capture the blue color range of trypan blue (typically 100-130)
439
- - **Saturation Min**: Minimum color saturation to distinguish from gray areas (typically 50+)
440
- - **Value Min/Max**: Brightness range for blue detection (typically 50-255)
441
-
442
- ### 3. Default Settings
443
- - The default settings work well for most trypan blue images
444
- - Fine-tune based on your specific staining conditions and lighting
 
 
 
445
 
446
  ### 4. Output
447
  - **Green overlay**: Live cells
 
119
  x_min, y_min, x_max, y_max = coords
120
  return image_np[y_min:y_max+1, x_min:x_max+1]
121
 
122
+ def classify_cells_by_blueness(image_np, masks, blue_threshold):
123
  """
124
+ Classify cells as dead (blue) or alive based on single blueness metric
125
 
126
  Args:
127
  image_np: RGB image array
128
  masks: Cellpose segmentation masks
129
+ blue_threshold: Single threshold value (0-100) for blueness detection
 
 
130
 
131
  Returns:
132
  dead_count, alive_count, colored_overlay
 
134
  # Convert RGB to HSV
135
  hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)
136
 
137
+ # Calculate blueness index for each pixel
138
+ hue = hsv[:, :, 0].astype(np.float32)
139
+ saturation = hsv[:, :, 1].astype(np.float32)
140
+
141
+ # Hue score: peaks around 115 (blue in HSV), drops off towards edges
142
+ # Handle hue wrap-around for blue detection (100-130 range)
143
+ hue_distance = np.minimum(np.abs(hue - 115), 180 - np.abs(hue - 115))
144
+ hue_score = np.maximum(0, 1 - hue_distance / 65) # 65 gives good blue range
145
+
146
+ # Combine hue proximity with saturation intensity
147
+ blueness = hue_score * (saturation / 255.0)
148
+
149
+ # Convert threshold from 0-100 to 0-1 scale
150
+ threshold = blue_threshold / 100.0
151
 
152
  # Get unique cell IDs (excluding background)
153
  cell_ids = np.unique(masks)
 
161
  # Get mask for this specific cell
162
  cell_mask = (masks == cell_id)
163
 
164
+ # Calculate average blueness for this cell
165
+ cell_blueness = np.mean(blueness[cell_mask])
 
166
 
167
+ # Classify based on threshold
168
+ if cell_blueness > threshold:
169
  dead_cells.append(cell_id)
170
  else:
171
  alive_cells.append(cell_id)
 
190
  return len(dead_cells), len(alive_cells), overlay
191
 
192
  @spaces.GPU
193
+ def segment_and_count_viability(editor_data, model_choice, blue_threshold, crop_coords=None):
194
  """
195
+ Segment cells and analyze viability using single blueness threshold
196
  """
197
  try:
198
  # Debug info
 
242
  # Run Cellpose on the selected region
243
  masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
244
 
245
+ # Classify cells by blueness
246
+ dead_count, alive_count, viability_overlay = classify_cells_by_blueness(
247
+ region_np, masks, blue_threshold
248
  )
249
 
250
  total_count = dead_count + alive_count
 
254
  overlay_image = Image.fromarray(viability_overlay)
255
 
256
  # Create info message
257
+ info_msg = f"Total cells: {total_count}\nAlive cells: {alive_count}\nDead cells: {dead_count}\nViability: {viability_percent:.1f}%\nBlue threshold: {blue_threshold}%"
258
  if region_coords:
259
  info_msg += f"\nProcessed region: {region_coords[0]},{region_coords[1]} to {region_coords[2]},{region_coords[3]}"
260
 
 
264
  return 0, 0, 0, 0.0, None, f"Error occurred: {str(e)}\nDebug info:\n{debug_info if 'debug_info' in locals() else 'No debug info'}"
265
 
266
  @spaces.GPU
267
+ def segment_with_coords_viability(image, model_choice, blue_threshold, crop_coords):
268
  """Segment using regular image input with manual coordinate specification and viability analysis"""
269
  if image is None:
270
  return 0, 0, 0, 0.0, None, "No image provided"
 
309
  # Run Cellpose
310
  masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
311
 
312
+ # Classify cells by blueness
313
+ dead_count, alive_count, viability_overlay = classify_cells_by_blueness(
314
+ image_np, masks, blue_threshold
315
  )
316
 
317
  total_count = dead_count + alive_count
 
319
 
320
  overlay_image = Image.fromarray(viability_overlay)
321
 
322
+ info_msg = f"Total cells: {total_count}\nAlive cells: {alive_count}\nDead cells: {dead_count}\nViability: {viability_percent:.1f}%\nBlue threshold: {blue_threshold}%"
323
  if crop_coords:
324
  info_msg += f"\nProcessed with coordinates: {crop_coords}"
325
 
 
351
  value="Hemocytometer Model"
352
  )
353
 
354
+ # Single blue threshold control
355
+ gr.Markdown("### Trypan Blue Threshold")
356
+ blue_threshold1 = gr.Slider(
357
+ 0, 100, value=25, step=1,
358
+ label="Blue Threshold (%)",
359
+ info="Higher values = more selective for blue cells"
360
+ )
 
 
 
361
 
362
  segment_btn1 = gr.Button("Analyze Viability", variant="primary")
363
 
 
373
 
374
  segment_btn1.click(
375
  fn=segment_and_count_viability,
376
+ inputs=[image_editor, model_dropdown1, blue_threshold1],
377
  outputs=[total_count_output1, alive_count_output1, dead_count_output1, viability_output1, overlay_output1, info_output1]
378
  )
379
 
 
396
  info="Format: x_min,y_min,x_max,y_max"
397
  )
398
 
399
+ # Single blue threshold control
400
+ gr.Markdown("### Trypan Blue Threshold")
401
+ blue_threshold2 = gr.Slider(
402
+ 0, 100, value=25, step=1,
403
+ label="Blue Threshold (%)",
404
+ info="Higher values = more selective for blue cells"
405
+ )
 
 
 
406
 
407
  segment_btn2 = gr.Button("Analyze Viability", variant="primary")
408
 
 
418
 
419
  segment_btn2.click(
420
  fn=segment_with_coords_viability,
421
+ inputs=[image_input, model_dropdown2, blue_threshold2, coord_input],
422
  outputs=[total_count_output2, alive_count_output2, dead_count_output2, viability_output2, overlay_output2, info_output2]
423
  )
424
 
 
431
  - Upload a trypan blue stained microscopy image
432
  - Dead cells should appear blue, alive cells should be unstained (clear)
433
 
434
+ ### 2. Blue Threshold Adjustment
435
+ - **Single Slider (0-100%)**: Adjusts sensitivity to blue coloration
436
+ - **Lower values (10-20%)**: More sensitive, detects lightly stained cells
437
+ - **Higher values (30-50%)**: More selective, only detects strongly blue cells
438
+ - **Default (25%)**: Works well for most standard trypan blue staining
439
+
440
+ ### 3. How the Blue Detection Works
441
+ - Analyzes hue (color) and saturation (intensity) in each cell
442
+ - Focuses on blue range around 115° in HSV color space
443
+ - Combines color proximity with saturation strength
444
+ - Calculates average "blueness" for each detected cell
445
 
446
  ### 4. Output
447
  - **Green overlay**: Live cells