LiangLabUMB commited on
Commit
f079690
·
verified ·
1 Parent(s): 798c897

Upload cellpose_viability_app.py

Browse files
Files changed (1) hide show
  1. cellpose_viability_app.py +539 -0
cellpose_viability_app.py ADDED
@@ -0,0 +1,539 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import spaces
3
+ from cellpose import models
4
+ import numpy as np
5
+ import cv2
6
+ import matplotlib.pyplot as plt
7
+ import tempfile
8
+ from PIL import Image
9
+ import io
10
+ from huggingface_hub import hf_hub_download
11
+
12
+ HF_REPO_ID = "myang4218/cellposemodel"
13
+ 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
21
+ current_masks = None
22
+ current_image = None
23
+ current_region_coords = None
24
+
25
+ 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():
35
+ info += f" {key}: {type(value)}\n"
36
+ if key == 'layers' and value:
37
+ info += f" Layers count: {len(value)}\n"
38
+ for i, layer in enumerate(value):
39
+ info += f" Layer {i}: {type(layer)}\n"
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
70
+ mask = selection_np[:, :, 3] > 0
71
+ else: # RGB
72
+ mask = np.any(selection_np > 0, axis=2)
73
+ else:
74
+ mask = selection_np > 0
75
+
76
+ coords = np.where(mask)
77
+ if len(coords[0]) > 0:
78
+ y_min, y_max = coords[0].min(), coords[0].max()
79
+ x_min, x_max = coords[1].min(), coords[1].max()
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
+ 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):
101
+ """Calculate blue intensity for each cell"""
102
+ if len(image_np.shape) == 2:
103
+ # Convert grayscale to RGB
104
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
105
+ elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
106
+ # Handle RGBA
107
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
108
+
109
+ # Calculate blue intensity for each cell
110
+ cell_blue_intensities = {}
111
+ unique_cells = np.unique(masks)
112
+
113
+ for cell_id in unique_cells:
114
+ if cell_id == 0: # Skip background
115
+ continue
116
+
117
+ cell_mask = masks == cell_id
118
+ cell_pixels = image_np[cell_mask]
119
+
120
+ if len(cell_pixels) > 0:
121
+ # Calculate mean blue intensity (blue channel)
122
+ blue_intensity = np.mean(cell_pixels[:, 2]) # Blue channel is index 2
123
+ cell_blue_intensities[cell_id] = blue_intensity
124
+
125
+ return cell_blue_intensities
126
+
127
+ def create_viability_overlay(image_np, masks, blue_threshold):
128
+ """Create overlay showing live/dead cells based on blue threshold"""
129
+
130
+ if masks is None or image_np is None:
131
+ return None, 0, 0
132
+
133
+ # Ensure image is RGB
134
+ if len(image_np.shape) == 2:
135
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
136
+ elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
137
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
138
+
139
+ # Calculate blue intensities
140
+ cell_blue_intensities = calculate_blue_intensity(image_np, masks)
141
+
142
+ # Create overlay - start with original image
143
+ overlay = image_np.copy().astype(np.float32)
144
+
145
+ live_count = 0
146
+ dead_count = 0
147
+
148
+ # Color cells based on viability
149
+ for cell_id, blue_intensity in cell_blue_intensities.items():
150
+ cell_mask = masks == cell_id
151
+
152
+ if blue_intensity > blue_threshold:
153
+ # Dead cell - color in red
154
+ overlay[cell_mask] = [255, 0, 0] # Red
155
+ dead_count += 1
156
+ else:
157
+ # Live cell - color in green
158
+ overlay[cell_mask] = [0, 255, 0] # Green
159
+ live_count += 1
160
+
161
+ # Blend with original image
162
+ alpha = 0.6 # Increased alpha for better visibility
163
+ final_overlay = (1 - alpha) * image_np.astype(np.float32) + alpha * overlay
164
+ final_overlay = np.clip(final_overlay, 0, 255).astype(np.uint8)
165
+
166
+ return Image.fromarray(final_overlay), live_count, dead_count
167
+
168
+ @spaces.GPU
169
+ def segment_cells(editor_data, model_choice, crop_coords=None):
170
+ """
171
+ First stage: Segment cells and store results for viability assessment
172
+ """
173
+ global current_masks, current_image, current_region_coords
174
+
175
+ try:
176
+ debug_info = debug_editor_data(editor_data)
177
+
178
+ # Load the model
179
+ model_filename = MODEL_OPTIONS[model_choice]
180
+ model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
181
+
182
+ if model_filename in loaded_models:
183
+ model = loaded_models[model_filename]
184
+ else:
185
+ model = models.CellposeModel(gpu=True, pretrained_model=model_path)
186
+ loaded_models[model_filename] = model
187
+
188
+ # Extract region from editor
189
+ region_np, region_coords = extract_region_from_editor(editor_data)
190
+
191
+ if region_np is None:
192
+ return 0, None, f"No image provided. Debug info:\n{debug_info}", gr.update(visible=False)
193
+
194
+ # Handle manual crop coordinates
195
+ if crop_coords and crop_coords.strip():
196
+ try:
197
+ coords = [int(x.strip()) for x in crop_coords.split(',')]
198
+ if len(coords) == 4:
199
+ x_min, y_min, x_max, y_max = coords
200
+ h, w = region_np.shape[:2]
201
+ x_min = max(0, min(x_min, w-1))
202
+ y_min = max(0, min(y_min, h-1))
203
+ x_max = max(x_min+1, min(x_max, w))
204
+ y_max = max(y_min+1, min(y_max, h))
205
+
206
+ region_np = region_np[y_min:y_max, x_min:x_max]
207
+ region_coords = (x_min, y_min, x_max, y_max)
208
+ except ValueError:
209
+ pass
210
+
211
+ # Process image format
212
+ if len(region_np.shape) == 2:
213
+ region_np = cv2.cvtColor(region_np, cv2.COLOR_GRAY2RGB)
214
+ elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
215
+ region_np = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
216
+
217
+ # Run Cellpose segmentation
218
+ masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
219
+
220
+ # Store results globally for viability assessment
221
+ current_masks = masks
222
+ current_image = region_np
223
+ current_region_coords = region_coords
224
+
225
+ # Count unique cells
226
+ cell_count = len(np.unique(masks)) - 1
227
+
228
+ # Create basic segmentation overlay
229
+ overlay = region_np.copy().astype(np.float32)
230
+
231
+ if masks.max() > 0:
232
+ np.random.seed(42)
233
+ colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
234
+ colors[0] = [0, 0, 0] # Background stays black
235
+
236
+ colored_mask = colors[masks]
237
+ alpha = 0.4
238
+ overlay = (1 - alpha) * overlay + alpha * colored_mask
239
+
240
+ overlay = np.clip(overlay, 0, 255).astype(np.uint8)
241
+ overlay_image = Image.fromarray(overlay)
242
+
243
+ info_msg = f"Segmentation complete! Found {cell_count} cells.\n"
244
+ if region_coords:
245
+ info_msg += f"Region: {region_coords}\n"
246
+ info_msg += f"Now use the blue threshold slider below for viability assessment."
247
+
248
+ # Make viability section visible
249
+ viability_section_update = gr.update(visible=True)
250
+
251
+ return cell_count, overlay_image, info_msg, viability_section_update
252
+
253
+ except Exception as e:
254
+ current_masks = None
255
+ current_image = None
256
+ current_region_coords = None
257
+ return 0, None, f"Error: {str(e)}", gr.update(visible=False)
258
+
259
+ def update_viability_assessment(blue_threshold):
260
+ """
261
+ Real-time viability assessment based on blue threshold
262
+ """
263
+ global current_masks, current_image
264
+
265
+ if current_masks is None or current_image is None:
266
+ return None, 0, 0, "Please run segmentation first"
267
+
268
+ try:
269
+ # Debug information
270
+ unique_cells = np.unique(current_masks)
271
+ total_cells = len(unique_cells) - 1 # Subtract background
272
+
273
+ debug_info = f"Debug - Total cells found: {total_cells}\n"
274
+ debug_info += f"Image shape: {current_image.shape}\n"
275
+ debug_info += f"Masks shape: {current_masks.shape}\n"
276
+ debug_info += f"Unique mask values: {unique_cells[:10]}...\n" # Show first 10
277
+
278
+ # Create viability overlay
279
+ viability_overlay, live_count, dead_count = create_viability_overlay(
280
+ current_image, current_masks, blue_threshold
281
+ )
282
+
283
+ total_count = live_count + dead_count
284
+ viability_info = f"Blue threshold: {blue_threshold}\n"
285
+ viability_info += f"Total cells: {total_count}\nLive (green): {live_count}\nDead (red): {dead_count}\n"
286
+
287
+ if total_count > 0:
288
+ viability_percentage = (live_count / total_count) * 100
289
+ viability_info += f"Viability: {viability_percentage:.1f}%\n"
290
+
291
+ viability_info += f"\n{debug_info}"
292
+
293
+ return viability_overlay, live_count, dead_count, viability_info
294
+
295
+ except Exception as e:
296
+ 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}"
297
+
298
+ @spaces.GPU
299
+ def segment_with_coords(image, model_choice, crop_coords):
300
+ """Alternative function for manual coordinates input"""
301
+ global current_masks, current_image, current_region_coords
302
+
303
+ if image is None:
304
+ return 0, None, "No image provided", gr.update(visible=False)
305
+
306
+ try:
307
+ # Load the model
308
+ model_filename = MODEL_OPTIONS[model_choice]
309
+ model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
310
+
311
+ if model_filename in loaded_models:
312
+ model = loaded_models[model_filename]
313
+ else:
314
+ model = models.CellposeModel(gpu=True, pretrained_model=model_path)
315
+ loaded_models[model_filename] = model
316
+
317
+ # Convert PIL Image to numpy array
318
+ image_np = np.array(image)
319
+
320
+ # Apply crop if coordinates are provided
321
+ if crop_coords and crop_coords.strip():
322
+ try:
323
+ coords = [int(x.strip()) for x in crop_coords.split(',')]
324
+ if len(coords) == 4:
325
+ x_min, y_min, x_max, y_max = coords
326
+ h, w = image_np.shape[:2]
327
+ x_min = max(0, min(x_min, w-1))
328
+ y_min = max(0, min(y_min, h-1))
329
+ x_max = max(x_min+1, min(x_max, w))
330
+ y_max = max(y_min+1, min(y_max, h))
331
+
332
+ image_np = image_np[y_min:y_max, x_min:x_max]
333
+ current_region_coords = (x_min, y_min, x_max, y_max)
334
+ except ValueError:
335
+ pass
336
+
337
+ # Process image format
338
+ if len(image_np.shape) == 2:
339
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
340
+ elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
341
+ image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
342
+
343
+ # Run Cellpose
344
+ masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
345
+
346
+ # Store results globally
347
+ current_masks = masks
348
+ current_image = image_np
349
+
350
+ # Count unique cells
351
+ cell_count = len(np.unique(masks)) - 1
352
+
353
+ # Create overlay
354
+ overlay = image_np.copy().astype(np.float32)
355
+
356
+ if masks.max() > 0:
357
+ np.random.seed(42)
358
+ colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
359
+ colors[0] = [0, 0, 0]
360
+
361
+ colored_mask = colors[masks]
362
+ alpha = 0.4
363
+ overlay = (1 - alpha) * overlay + alpha * colored_mask
364
+
365
+ overlay = np.clip(overlay, 0, 255).astype(np.uint8)
366
+ overlay_image = Image.fromarray(overlay)
367
+
368
+ info_msg = f"Segmentation complete! Found {cell_count} cells.\n"
369
+ info_msg += f"Now use the blue threshold slider below for viability assessment."
370
+
371
+ return cell_count, overlay_image, info_msg, gr.update(visible=True)
372
+
373
+ except Exception as e:
374
+ current_masks = None
375
+ current_image = None
376
+ return 0, None, f"Error: {str(e)}", gr.update(visible=False)
377
+
378
+ # Create the Gradio interface
379
+ with gr.Blocks(title="Cell Counter with Real-time Viability Assessment", theme=gr.themes.Soft()) as demo:
380
+ gr.Markdown("# Cell Counter with Real-time Viability Assessment")
381
+ gr.Markdown("Upload a microscopy image, run segmentation, then use the blue threshold slider for real-time viability assessment.")
382
+
383
+ with gr.Tab("Image Editor (Draw Selection)"):
384
+ gr.Markdown("### Step 1: Draw selection and run segmentation")
385
+
386
+ with gr.Row():
387
+ with gr.Column():
388
+ image_editor = gr.ImageEditor(
389
+ label="Draw selection on image",
390
+ type="pil",
391
+ brush=gr.Brush(colors=["#ff0000"], color_mode="fixed", default_size=20),
392
+ eraser=gr.Eraser(default_size=20)
393
+ )
394
+ model_dropdown1 = gr.Dropdown(
395
+ choices=list(MODEL_OPTIONS.keys()),
396
+ label="Select Model",
397
+ value="Hemocytometer Model"
398
+ )
399
+ segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
400
+
401
+ with gr.Column():
402
+ cell_count_output1 = gr.Number(label="Total Cells Detected", precision=0)
403
+ overlay_output1 = gr.Image(type="pil", label="Segmentation Result")
404
+ info_output1 = gr.Textbox(label="Processing Info", lines=3)
405
+
406
+ # Viability Assessment Section
407
+ with gr.Group(visible=False) as viability_section1:
408
+ gr.Markdown("### Step 2: Real-time Viability Assessment")
409
+ gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
410
+
411
+ with gr.Row():
412
+ with gr.Column():
413
+ blue_threshold_slider1 = gr.Slider(
414
+ minimum=0,
415
+ maximum=255,
416
+ value=100,
417
+ step=1,
418
+ label="Blue Threshold",
419
+ info="Higher values = more cells classified as dead"
420
+ )
421
+
422
+ with gr.Column():
423
+ live_count_output1 = gr.Number(label="Live Cells (Green)", precision=0)
424
+ dead_count_output1 = gr.Number(label="Dead Cells (Red)", precision=0)
425
+
426
+ viability_overlay1 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
427
+ viability_info1 = gr.Textbox(label="Viability Statistics", lines=4)
428
+
429
+ # Event handlers
430
+ segment_btn1.click(
431
+ fn=segment_cells,
432
+ inputs=[image_editor, model_dropdown1],
433
+ outputs=[cell_count_output1, overlay_output1, info_output1, viability_section1]
434
+ ).then( # Chain the viability assessment after segmentation
435
+ fn=update_viability_assessment,
436
+ inputs=[blue_threshold_slider1],
437
+ outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
438
+ )
439
+
440
+ blue_threshold_slider1.change(
441
+ fn=update_viability_assessment,
442
+ inputs=[blue_threshold_slider1],
443
+ outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_info1]
444
+ )
445
+
446
+ with gr.Tab("Manual Coordinates"):
447
+ gr.Markdown("### Step 1: Upload image and run segmentation")
448
+
449
+ with gr.Row():
450
+ with gr.Column():
451
+ image_input = gr.Image(type="pil", label="Microscopy Image")
452
+ model_dropdown2 = gr.Dropdown(
453
+ choices=list(MODEL_OPTIONS.keys()),
454
+ label="Select Model",
455
+ value="Hemocytometer Model"
456
+ )
457
+ coord_input = gr.Textbox(
458
+ label="Crop Coordinates (optional)",
459
+ placeholder="e.g., 100,100,400,400",
460
+ info="Format: x_min,y_min,x_max,y_max"
461
+ )
462
+ segment_btn2 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
463
+
464
+ with gr.Column():
465
+ cell_count_output2 = gr.Number(label="Total Cells Detected", precision=0)
466
+ overlay_output2 = gr.Image(type="pil", label="Segmentation Result")
467
+ info_output2 = gr.Textbox(label="Processing Info", lines=3)
468
+
469
+ # Viability Assessment Section
470
+ with gr.Group(visible=False) as viability_section2:
471
+ gr.Markdown("### Step 2: Real-time Viability Assessment")
472
+ gr.Markdown("Adjust the blue threshold to classify cells as live (green) or dead (red). Higher values = more blue = more dead.")
473
+
474
+ with gr.Row():
475
+ with gr.Column():
476
+ blue_threshold_slider2 = gr.Slider(
477
+ minimum=0,
478
+ maximum=255,
479
+ value=100,
480
+ step=1,
481
+ label="Blue Threshold",
482
+ info="Higher values = more cells classified as dead"
483
+ )
484
+
485
+ with gr.Column():
486
+ live_count_output2 = gr.Number(label="Live Cells (Green)", precision=0)
487
+ dead_count_output2 = gr.Number(label="Dead Cells (Red)", precision=0)
488
+
489
+ viability_overlay2 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
490
+ viability_info2 = gr.Textbox(label="Viability Statistics", lines=4)
491
+
492
+ # Event handlers
493
+ segment_btn2.click(
494
+ fn=segment_with_coords,
495
+ inputs=[image_input, model_dropdown2, coord_input],
496
+ outputs=[cell_count_output2, overlay_output2, info_output2, viability_section2]
497
+ ).then( # Chain the viability assessment after segmentation
498
+ fn=update_viability_assessment,
499
+ inputs=[blue_threshold_slider2],
500
+ outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
501
+ )
502
+
503
+ blue_threshold_slider2.change(
504
+ fn=update_viability_assessment,
505
+ inputs=[blue_threshold_slider2],
506
+ outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_info2]
507
+ )
508
+
509
+ # Instructions
510
+ with gr.Accordion("Instructions", open=False):
511
+ gr.Markdown("""
512
+ ### How to use:
513
+
514
+ 1. **Upload and Segment**:
515
+ - Upload your microscopy image
516
+ - Select a model (Hemocytometer Model for blood cells)
517
+ - Draw a selection region or specify coordinates
518
+ - Click "Run Segmentation"
519
+
520
+ 2. **Viability Assessment**:
521
+ - After segmentation, the viability section will appear
522
+ - Use the blue threshold slider to classify cells in real-time
523
+ - Green cells = Live (low blue intensity)
524
+ - Red cells = Dead (high blue intensity)
525
+ - Adjust the threshold until the classification looks correct
526
+
527
+ 3. **Interpreting Results**:
528
+ - The app calculates viability percentage automatically
529
+ - Live cells appear green, dead cells appear red
530
+ - Statistics show total counts and viability percentage
531
+
532
+ ### Tips:
533
+ - Start with a threshold around 100 and adjust based on your staining
534
+ - Dead cells typically have higher blue intensity due to trypan blue uptake
535
+ - The threshold is applied in real-time without re-running segmentation
536
+ """)
537
+
538
+ if __name__ == "__main__":
539
+ demo.launch()