LiangLabUMB commited on
Commit
14483a4
·
verified ·
1 Parent(s): 1dc95db

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1166
app.py DELETED
@@ -1,1166 +0,0 @@
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, ImageDraw
9
- import io
10
- from huggingface_hub import hf_hub_download
11
- import base64
12
-
13
- HF_REPO_ID = "myang4218/cellposemodel"
14
- MODEL_OPTIONS = {
15
- "Hemocytometer Model": "hemocytometermodel.npy",
16
- "General Model": "generalmodel.npy"
17
- }
18
-
19
- loaded_models = {}
20
-
21
- # ---- mobile-safe size limits (aggressive for Safari) ----
22
- MAX_SIDE = 1024
23
- MAX_PIXELS = 1024 * 1024
24
-
25
-
26
- def safe_resize(image_np):
27
- """
28
- Downscale image to fit within MAX_SIDE and MAX_PIXELS while
29
- preserving aspect ratio. Works for RGB / RGBA / grayscale.
30
- """
31
- h, w = image_np.shape[:2]
32
- total = h * w
33
-
34
- if max(h, w) <= MAX_SIDE and total <= MAX_PIXELS:
35
- return image_np
36
-
37
- # compute scale
38
- scale_side = MAX_SIDE / max(h, w)
39
- scale_pixels = (MAX_PIXELS / total) ** 0.5
40
- scale = min(scale_side, scale_pixels)
41
-
42
- new_w = max(1, int(w * scale))
43
- new_h = max(1, int(h * scale))
44
-
45
- return cv2.resize(image_np, (new_w, new_h), interpolation=cv2.INTER_AREA)
46
-
47
-
48
- def draw_exclusion_overlay(image_np, left_width_pct, top_width_pct):
49
-
50
- h, w = image_np.shape[:2]
51
-
52
- # Convert to PIL for drawing
53
- img_pil = Image.fromarray(image_np)
54
- draw = ImageDraw.Draw(img_pil, 'RGBA')
55
-
56
- # Calculate pixel widths from percentages
57
- left_px = int(w * left_width_pct / 100)
58
- top_px = int(h * top_width_pct / 100)
59
-
60
- # Draw overlays for exclusion zones
61
- if left_px > 0:
62
- # Left exclusion zone
63
- draw.rectangle(
64
- [(0, 0), (left_px, h)],
65
- fill=(255, 0, 0, 80) # Semi-transparent red
66
- )
67
- # border line
68
- draw.line([(left_px, 0), (left_px, h)], fill=(255, 0, 0, 255), width=3)
69
-
70
- if top_px > 0:
71
- # Top exclusion zone
72
- draw.rectangle(
73
- [(0, 0), (w, top_px)],
74
- fill=(255, 0, 0, 80) # Semi-transparent red
75
- )
76
- # border line
77
- draw.line([(0, top_px), (w, top_px)], fill=(255, 0, 0, 255), width=3)
78
-
79
- return np.array(img_pil)
80
-
81
-
82
- def apply_stereological_exclusion(masks, left_width_pct, top_width_pct):
83
- h, w = masks.shape
84
-
85
- # Calculate pixel widths from percentages
86
- left_px = int(w * left_width_pct / 100)
87
- top_px = int(h * top_width_pct / 100)
88
-
89
- filtered_masks = masks.copy()
90
- cell_ids = np.unique(masks)
91
- cell_ids = cell_ids[cell_ids > 0]
92
-
93
- excluded_cells = []
94
- included_cells = []
95
-
96
- for cell_id in cell_ids:
97
- cell_mask = (masks == cell_id)
98
-
99
- # Get cell boundary coordinates
100
- rows, cols = np.where(cell_mask)
101
-
102
- # Check if cell touches left exclusion zone
103
- touches_left = np.any(cols < left_px) if left_px > 0 else False
104
-
105
- # Check if cell touches top exclusion zone
106
- touches_top = np.any(rows < top_px) if top_px > 0 else False
107
-
108
- # Exclude if touching left or top
109
- if touches_left or touches_top:
110
- filtered_masks[cell_mask] = 0
111
- excluded_cells.append(cell_id)
112
- else:
113
- included_cells.append(cell_id)
114
-
115
- # Renumber remaining cells
116
- unique_ids = np.unique(filtered_masks)
117
- unique_ids = unique_ids[unique_ids > 0]
118
-
119
- renumbered_masks = np.zeros_like(filtered_masks)
120
- for new_id, old_id in enumerate(unique_ids, start=1):
121
- renumbered_masks[filtered_masks == old_id] = new_id
122
-
123
- return renumbered_masks, len(excluded_cells), len(included_cells)
124
-
125
-
126
- def classify_cells_by_blueness(image_np, masks, blue_threshold):
127
- """
128
- Classify cells as dead (blue) or alive based on single blueness metric
129
-
130
- Args:
131
- image_np: RGB image array
132
- masks: Cellpose segmentation masks
133
- blue_threshold: Single threshold value (0-100) for blueness detection
134
-
135
- Returns:
136
- dead_count, alive_count, colored_overlay
137
- """
138
-
139
- if len(image_np.shape) == 2:
140
- image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
141
- elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
142
- image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
143
-
144
-
145
- hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)
146
-
147
- # Calculate blueness index for each pixel
148
- hue = hsv[:, :, 0].astype(np.float32)
149
- saturation = hsv[:, :, 1].astype(np.float32)
150
-
151
- # Hue score: peaks around 115 (blue in HSV), drops off towards edges
152
- # Handle hue wrap-around for blue detection (100-130 range)
153
- hue_distance = np.minimum(np.abs(hue - 115), 180 - np.abs(hue - 115))
154
- hue_score = np.maximum(0, 1 - hue_distance / 65) # 65 gives good blue range
155
-
156
- # Combine hue proximity with saturation intensity
157
- blueness = hue_score * (saturation / 255.0)
158
-
159
- # Convert threshold from 0-100 to 0-1 scale
160
- threshold = blue_threshold / 100.0
161
-
162
- # Get unique cell IDs
163
- cell_ids = np.unique(masks)
164
- cell_ids = cell_ids[cell_ids > 0]
165
-
166
- dead_cells = []
167
- alive_cells = []
168
-
169
- # Classify each cell
170
- for cell_id in cell_ids:
171
-
172
- cell_mask = (masks == cell_id)
173
- cell_blueness = np.mean(blueness[cell_mask])
174
-
175
- if cell_blueness > threshold:
176
- dead_cells.append(cell_id)
177
- else:
178
- alive_cells.append(cell_id)
179
-
180
- # Create colored overlay
181
- overlay = image_np.copy().astype(np.float32) # Ensure float for blending
182
-
183
- # Color dead cells red, alive cells green
184
- for cell_id in dead_cells:
185
- cell_mask = (masks == cell_id)
186
- overlay[cell_mask] = [255, 0, 0]
187
-
188
- for cell_id in alive_cells:
189
- cell_mask = (masks == cell_id)
190
- overlay[cell_mask] = [0, 255, 0]
191
-
192
- # Blend with original image
193
- alpha = 0.4
194
- final_overlay = (1 - alpha) * image_np.astype(np.float32) + alpha * overlay
195
- final_overlay = np.clip(final_overlay, 0, 255).astype(np.uint8)
196
-
197
- return len(dead_cells), len(alive_cells), final_overlay
198
-
199
-
200
- def measure_confluency(masks, image_np):
201
- tot_pixels = image_np.shape[0] * image_np.shape[1]
202
- cell_pixels = np.count_nonzero(masks)
203
- confluency = cell_pixels / tot_pixels * 100
204
- return confluency
205
-
206
- def filter_mask_by_size(masks, minimum_pixels):
207
- filtered_masks = masks.copy()
208
- cell_ids = np.unique(masks)
209
- cell_ids = cell_ids[cell_ids > 0]
210
-
211
- removed_count = 0
212
-
213
- for cell_id in cell_ids:
214
- cell_mask = (masks == cell_id)
215
- cell_pixels = np.count_nonzero(cell_mask)
216
- if cell_pixels < minimum_pixels:
217
- filtered_masks[cell_mask] = 0
218
- removed_count += 1
219
-
220
- unique_ids = np.unique(filtered_masks)
221
- unique_ids = unique_ids[unique_ids > 0]
222
-
223
- renumbered_masks = np.zeros_like(filtered_masks)
224
- for new_id, old_id in enumerate(unique_ids, start=1):
225
- renumbered_masks[filtered_masks == old_id] = new_id
226
-
227
- return renumbered_masks, removed_count
228
-
229
-
230
- def filter_mask_by_maxsize(masks, maximum_pixels):
231
- filtered_masks = masks.copy()
232
- cell_ids = np.unique(masks)
233
- cell_ids = cell_ids[cell_ids > 0]
234
-
235
- removed_count = 0
236
- for cell_id in cell_ids:
237
- cell_mask = (masks == cell_id)
238
- cell_pixels = np.count_nonzero(cell_mask)
239
- if cell_pixels > maximum_pixels:
240
- filtered_masks[cell_mask] = 0
241
- removed_count += 1
242
-
243
- unique_ids = np.unique(filtered_masks)
244
- unique_ids = unique_ids[unique_ids > 0]
245
-
246
- renumbered_masks = np.zeros_like(filtered_masks)
247
- for new_id, old_id in enumerate(unique_ids, start=1):
248
- renumbered_masks[filtered_masks == old_id] = new_id
249
-
250
- return renumbered_masks, removed_count
251
-
252
-
253
- def rec_min_size(masks, q=25):
254
- ids = np.unique(masks)
255
- ids = ids[ids > 0]
256
- if len(ids) == 0:
257
- return 0
258
- sizes = np.array([np.count_nonzero(masks == cid) for cid in ids])
259
- return int(round(np.percentile(sizes, q)))
260
-
261
-
262
- def toggle_stereological_mode(use_stereology):
263
- """Show/hide stereological controls based on checkbox"""
264
- return gr.update(visible=use_stereology)
265
-
266
-
267
- def update_exclusion_preview(image, left_width, top_width):
268
- """Update the preview image with exclusion zone overlay"""
269
- if image is None:
270
- return None
271
-
272
- image_np = np.array(image)
273
- overlay = draw_exclusion_overlay(image_np, left_width, top_width)
274
- return Image.fromarray(overlay)
275
-
276
-
277
- @spaces.GPU
278
- def run_segmentation(image, model_choice, min_cell_size, max_cell_size,
279
- use_stereology, left_exclusion, top_exclusion):
280
- image_np = np.array(image)
281
- image_np = safe_resize(image_np)
282
-
283
- try:
284
- model_filename = MODEL_OPTIONS[model_choice]
285
- model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
286
-
287
- if model_filename in loaded_models:
288
- model = loaded_models[model_filename]
289
- else:
290
- model = models.CellposeModel(gpu=True, pretrained_model=model_path)
291
- loaded_models[model_filename] = model
292
-
293
- # Process image format to RGB
294
- if len(image_np.shape) == 2:
295
- processed_image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
296
- elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
297
- processed_image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
298
- else:
299
- processed_image_np = image_np
300
-
301
- # Run Cellpose segmentation
302
- masks_raw, flows, styles = model.eval(processed_image_np, diameter=None, channels=[0, 0])
303
-
304
- ids = np.unique(masks_raw)
305
- ids = ids[ids > 0]
306
-
307
- sizes = np.array([np.count_nonzero(masks_raw == cid) for cid in ids])
308
-
309
- print("num_cells:", len(ids))
310
- print("mean:", sizes.mean() if len(sizes) > 0 else 0)
311
- print("median:", np.median(sizes) if len(sizes) > 0 else 0)
312
- print("p90:", np.percentile(sizes, 90) if len(sizes) > 0 else 0)
313
- print("max:", sizes.max() if len(sizes) > 0 else 0)
314
-
315
- # Compute recommendation from RAW masks
316
- recommend_min = rec_min_size(masks_raw)
317
-
318
- # If user sets slider to 0, use the recommendation
319
- min_used = recommend_min if (min_cell_size == 0) else int(min_cell_size)
320
-
321
- # Apply filters
322
- masks = masks_raw.copy()
323
- removed_small = 0
324
- removed_large = 0
325
-
326
- if min_used > 0:
327
- masks, removed_small = filter_mask_by_size(masks, min_used)
328
-
329
- if max_cell_size > 0:
330
- masks, removed_large = filter_mask_by_maxsize(masks, int(max_cell_size))
331
-
332
- # Apply stereological exclusion if enabled
333
- excluded_count = 0
334
- if use_stereology:
335
- masks, excluded_count, included_count = apply_stereological_exclusion(
336
- masks, left_exclusion, top_exclusion
337
- )
338
-
339
- filter_msg = ""
340
- if removed_small:
341
- filter_msg += f"Removed {removed_small} small objects (< {min_used} pixels).\n"
342
- if removed_large:
343
- filter_msg += f"Removed {removed_large} large objects (> {int(max_cell_size)} pixels).\n"
344
- if use_stereology and excluded_count > 0:
345
- filter_msg += f"Stereological exclusion: {excluded_count} cells excluded (touching left/top zones).\n"
346
-
347
- cell_count = len(np.unique(masks)) - 1
348
- confluency = measure_confluency(masks, processed_image_np)
349
-
350
- # Create a basic segmentation overlay (without viability)
351
- segmentation_overlay = processed_image_np.copy().astype(np.float32)
352
- if masks.max() > 0:
353
- np.random.seed(42) # For consistent random colors
354
- colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
355
- colors[0] = [0, 0, 0]
356
- colored_mask = colors[masks]
357
- alpha = 0.4
358
- segmentation_overlay = (1 - alpha) * segmentation_overlay + alpha * colored_mask
359
- segmentation_overlay = np.clip(segmentation_overlay, 0, 255).astype(np.uint8)
360
-
361
- # Add exclusion zone overlay if stereology is enabled
362
- if use_stereology:
363
- segmentation_overlay = draw_exclusion_overlay(segmentation_overlay, left_exclusion, top_exclusion)
364
-
365
- info_msg = ""
366
- if filter_msg:
367
- info_msg += filter_msg
368
- info_msg += f"Segmentation complete! Found {cell_count} cells.\n"
369
- info_msg += f"Confluency: {confluency:.1f}%\n"
370
- if use_stereology:
371
- info_msg += f"Stereological counting enabled (Left: {left_exclusion}%, Top: {top_exclusion}%)\n"
372
- info_msg += "Now adjust the Blue Threshold for viability assessment."
373
-
374
- return (
375
- cell_count,
376
- Image.fromarray(segmentation_overlay),
377
- info_msg,
378
- gr.update(visible=True),
379
- pack_array(masks),
380
- pack_array(processed_image_np),
381
- confluency,
382
- gr.update(value=recommend_min), # update slider display to recommended
383
- )
384
-
385
- except Exception as e:
386
- import traceback
387
- traceback.print_exc()
388
- return (
389
- 0,
390
- None,
391
- f"Error during segmentation: {str(e)}",
392
- gr.update(visible=False),
393
- None,
394
- None,
395
- 0.0,
396
- gr.update(),
397
- )
398
-
399
-
400
- def update_viability_realtime(blue_threshold, stored_masks, stored_image_np):
401
- # avoid unpacking None (e.g. slider moved before segmentation)
402
- if stored_masks is None or stored_image_np is None:
403
- return None, 0, 0, 0.0, "Please run segmentation first."
404
-
405
- stored_masks = unpack_array(stored_masks)
406
- stored_image_np = unpack_array(stored_image_np)
407
-
408
- try:
409
- dead_count, alive_count, viability_overlay_np = classify_cells_by_blueness(
410
- stored_image_np, stored_masks, blue_threshold
411
- )
412
-
413
- total_count = alive_count + dead_count
414
- viability_percent = (alive_count / total_count * 100) if total_count > 0 else 0.0
415
- confluency = measure_confluency(stored_masks, stored_image_np)
416
-
417
- overlay_image = Image.fromarray(viability_overlay_np)
418
- info_msg = f"Total cells: {total_count}\nLive (green): {alive_count}\nDead (red): {dead_count}\n"
419
- info_msg += f"Viability: {viability_percent:.1f}%\nConfluency: {confluency:.1f}%\nBlue threshold: {blue_threshold}%"
420
-
421
- return overlay_image, alive_count, dead_count, viability_percent, info_msg
422
-
423
- except Exception as e:
424
- return None, 0, 0, 0.0, f"Error updating viability: {str(e)}"
425
-
426
-
427
- def pack_array(arr):
428
- pil = Image.fromarray(arr.astype(np.uint8))
429
- buf = io.BytesIO()
430
- pil.save(buf, format="PNG")
431
- return buf.getvalue()
432
-
433
-
434
- def unpack_array(data):
435
- return np.array(Image.open(io.BytesIO(data)))
436
-
437
-
438
- # Gradio interface
439
- with gr.Blocks(
440
- title="CellposeCellCounter",
441
- theme=gr.themes.Soft(),
442
- ) as demo:
443
- gr.Markdown("# CellposeCellCounter")
444
- gr.Markdown("For accurate cell confluency, crop the image to display only desired area. Note that some image file types are not yet supported. PNG and JPEG are preferred.")
445
-
446
- # Define State components to store masks and image data across function calls
447
- masks_state = gr.State(value=None)
448
- image_state = gr.State(value=None)
449
-
450
- with gr.Tab("Tab 1"):
451
- gr.Markdown("Run segmentation")
452
-
453
- with gr.Row():
454
- with gr.Column():
455
- img_input = gr.Image(
456
- type="pil",
457
- label="Microscopy image",
458
- image_mode="RGB",
459
- height=512
460
- )
461
-
462
- model_dropdown1 = gr.Dropdown(
463
- choices=list(MODEL_OPTIONS.keys()),
464
- label="Select Model",
465
- value="Hemocytometer Model"
466
- )
467
-
468
- min_size_slider1 = gr.Slider(
469
- minimum=0,
470
- maximum=500,
471
- value=0,
472
- step=10,
473
- label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
474
- )
475
-
476
- max_size_slider1 = gr.Slider(
477
- minimum=0,
478
- maximum=10000,
479
- value=10000,
480
- step=10,
481
- label="Maximum Cell Size (pixels)",
482
- )
483
-
484
- # Stereological counting option
485
- gr.Markdown("### Stereological Counting")
486
- use_stereology_checkbox = gr.Checkbox(
487
- label="Enable Stereological Counting",
488
- value=False,
489
- info="Use unbiased stereological rules for cell counting"
490
- )
491
-
492
- # Stereological controls (initially hidden)
493
- with gr.Group(visible=False) as stereology_controls:
494
- gr.Markdown("""
495
- **Stereological Counting Rules:**
496
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
497
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
498
- - This provides unbiased counting for quantification
499
- """)
500
-
501
- exclusion_preview = gr.Image(
502
- type="pil",
503
- label="Exclusion Zone Preview (Red = Excluded)",
504
- height=500
505
- )
506
-
507
- left_exclusion_slider = gr.Slider(
508
- minimum=0,
509
- maximum=50,
510
- value=10,
511
- step=1,
512
- label="Left Exclusion Width (%)",
513
- info="Width of left exclusion zone"
514
- )
515
-
516
- top_exclusion_slider = gr.Slider(
517
- minimum=0,
518
- maximum=50,
519
- value=10,
520
- step=1,
521
- label="Top Exclusion Width (%)",
522
- info="Width of top exclusion zone"
523
- )
524
-
525
- segment_btn1 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
526
-
527
- with gr.Column():
528
- cell_count_output1 = gr.Number(label="Total Cells Detected", precision=0)
529
- confluency_output1 = gr.Number(label="Confluency (%)", precision=1)
530
- overlay_output1 = gr.Image(type="pil", label="Segmentation Result")
531
- info_output1 = gr.Textbox(label="Processing Info", lines=4)
532
-
533
- # Viability Assessment Section
534
- with gr.Group(visible=False) as viability_section1:
535
- gr.Markdown("### Viability Assessment (Trypan Blue)")
536
- gr.Markdown("Adjust the threshold to classify cells as live (green) or dead (red).")
537
-
538
- with gr.Row():
539
- with gr.Column():
540
- blue_threshold1 = gr.Slider(
541
- minimum=0,
542
- maximum=100,
543
- value=25,
544
- step=1,
545
- label="Blue Threshold (%)",
546
- info="Higher values = more selective for blue cells"
547
- )
548
-
549
- with gr.Column():
550
- live_count_output1 = gr.Number(label="Live Cells (Green)", precision=0)
551
- dead_count_output1 = gr.Number(label="Dead Cells (Red)", precision=0)
552
-
553
- viability_overlay1 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
554
- viability_percent_output1 = gr.Number(label="Viability (%)", precision=1)
555
- viability_info1 = gr.Textbox(label="Analysis Results", lines=5)
556
-
557
- # Event handlers
558
-
559
- # Toggle stereological controls visibility
560
- use_stereology_checkbox.change(
561
- fn=toggle_stereological_mode,
562
- inputs=[use_stereology_checkbox],
563
- outputs=[stereology_controls]
564
- )
565
-
566
- # Update exclusion preview when image is uploaded or sliders change
567
- img_input.change(
568
- fn=update_exclusion_preview,
569
- inputs=[img_input, left_exclusion_slider, top_exclusion_slider],
570
- outputs=[exclusion_preview]
571
- )
572
-
573
- left_exclusion_slider.change(
574
- fn=update_exclusion_preview,
575
- inputs=[img_input, left_exclusion_slider, top_exclusion_slider],
576
- outputs=[exclusion_preview]
577
- )
578
-
579
- top_exclusion_slider.change(
580
- fn=update_exclusion_preview,
581
- inputs=[img_input, left_exclusion_slider, top_exclusion_slider],
582
- outputs=[exclusion_preview]
583
- )
584
-
585
- # Run segmentation
586
- segment_btn1.click(
587
- fn=run_segmentation,
588
- inputs=[
589
- img_input,
590
- model_dropdown1,
591
- min_size_slider1,
592
- max_size_slider1,
593
- use_stereology_checkbox,
594
- left_exclusion_slider,
595
- top_exclusion_slider
596
- ],
597
- outputs=[
598
- cell_count_output1,
599
- overlay_output1,
600
- info_output1,
601
- viability_section1,
602
- masks_state,
603
- image_state,
604
- confluency_output1,
605
- min_size_slider1
606
- ]
607
- ).then( # Chain the initial viability assessment after segmentation
608
- fn=update_viability_realtime,
609
- inputs=[blue_threshold1, masks_state, image_state],
610
- outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_percent_output1, viability_info1]
611
- )
612
-
613
- # Slider changes update viability in real-time
614
- blue_threshold1.change(
615
- fn=update_viability_realtime,
616
- inputs=[blue_threshold1, masks_state, image_state],
617
- outputs=[viability_overlay1, live_count_output1, dead_count_output1, viability_percent_output1, viability_info1]
618
- )
619
- with gr.Tab("Tab 2"):
620
- gr.Markdown("Run segmentation")
621
-
622
- with gr.Row():
623
- with gr.Column():
624
- img_input2 = gr.Image(
625
- type="pil",
626
- label="Microscopy image",
627
- image_mode="RGB",
628
- height=512
629
- )
630
-
631
- model_dropdown2 = gr.Dropdown(
632
- choices=list(MODEL_OPTIONS.keys()),
633
- label="Select Model",
634
- value="Hemocytometer Model"
635
- )
636
-
637
- min_size_slider2 = gr.Slider(
638
- minimum=0,
639
- maximum=500,
640
- value=0,
641
- step=10,
642
- label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
643
- )
644
-
645
- max_size_slider2 = gr.Slider(
646
- minimum=0,
647
- maximum=10000,
648
- value=10000,
649
- step=10,
650
- label="Maximum Cell Size (pixels)",
651
- )
652
-
653
- # Stereological counting option
654
- gr.Markdown("### Stereological Counting")
655
- use_stereology_checkbox2 = gr.Checkbox(
656
- label="Enable Stereological Counting",
657
- value=False,
658
- info="Use unbiased stereological rules for cell counting"
659
- )
660
-
661
- # Stereological controls (initially hidden)
662
- with gr.Group(visible=False) as stereology_controls2:
663
- gr.Markdown("""
664
- **Stereological Counting Rules:**
665
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
666
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
667
- - This provides unbiased counting for quantification
668
- """)
669
-
670
- exclusion_preview2 = gr.Image(
671
- type="pil",
672
- label="Exclusion Zone Preview (Red = Excluded)",
673
- height=500
674
- )
675
-
676
- left_exclusion_slider2 = gr.Slider(
677
- minimum=0,
678
- maximum=50,
679
- value=10,
680
- step=1,
681
- label="Left Exclusion Width (%)",
682
- info="Width of left exclusion zone"
683
- )
684
-
685
- top_exclusion_slider2 = gr.Slider(
686
- minimum=0,
687
- maximum=50,
688
- value=10,
689
- step=1,
690
- label="Top Exclusion Width (%)",
691
- info="Width of top exclusion zone"
692
- )
693
-
694
- segment_btn2 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
695
-
696
- with gr.Column():
697
- cell_count_output2 = gr.Number(label="Total Cells Detected", precision=0)
698
- confluency_output2 = gr.Number(label="Confluency (%)", precision=1)
699
- overlay_output2 = gr.Image(type="pil", label="Segmentation Result")
700
- info_output2 = gr.Textbox(label="Processing Info", lines=4)
701
-
702
- # Viability Assessment Section
703
- with gr.Group(visible=False) as viability_section2:
704
- gr.Markdown("### Viability Assessment (Trypan Blue)")
705
- gr.Markdown("Adjust the threshold to classify cells as live (green) or dead (red).")
706
-
707
- with gr.Row():
708
- with gr.Column():
709
- blue_threshold2 = gr.Slider(
710
- minimum=0,
711
- maximum=100,
712
- value=25,
713
- step=1,
714
- label="Blue Threshold (%)",
715
- info="Higher values = more selective for blue cells"
716
- )
717
-
718
- with gr.Column():
719
- live_count_output2 = gr.Number(label="Live Cells (Green)", precision=0)
720
- dead_count_output2 = gr.Number(label="Dead Cells (Red)", precision=0)
721
-
722
- viability_overlay2 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
723
- viability_percent_output2 = gr.Number(label="Viability (%)", precision=1)
724
- viability_info2 = gr.Textbox(label="Analysis Results", lines=5)
725
-
726
- # Event handlers
727
-
728
- # Toggle stereological controls visibility
729
- use_stereology_checkbox2.change(
730
- fn=toggle_stereological_mode,
731
- inputs=[use_stereology_checkbox2],
732
- outputs=[stereology_controls2]
733
- )
734
-
735
- # Update exclusion preview when image is uploaded or sliders change
736
- img_input2.change(
737
- fn=update_exclusion_preview,
738
- inputs=[img_input2, left_exclusion_slider2, top_exclusion_slider2],
739
- outputs=[exclusion_preview2]
740
- )
741
-
742
- left_exclusion_slider2.change(
743
- fn=update_exclusion_preview,
744
- inputs=[img_input2, left_exclusion_slider2, top_exclusion_slider2],
745
- outputs=[exclusion_preview2]
746
- )
747
-
748
- top_exclusion_slider2.change(
749
- fn=update_exclusion_preview,
750
- inputs=[img_input2, left_exclusion_slider2, top_exclusion_slider2],
751
- outputs=[exclusion_preview2]
752
- )
753
-
754
- # Run segmentation
755
- segment_btn2.click(
756
- fn=run_segmentation,
757
- inputs=[
758
- img_input2,
759
- model_dropdown2,
760
- min_size_slider2,
761
- max_size_slider2,
762
- use_stereology_checkbox2,
763
- left_exclusion_slider2,
764
- top_exclusion_slider2
765
- ],
766
- outputs=[
767
- cell_count_output2,
768
- overlay_output2,
769
- info_output2,
770
- viability_section2,
771
- masks_state,
772
- image_state,
773
- confluency_output2,
774
- min_size_slider2
775
- ]
776
- ).then( # Chain the initial viability assessment after segmentation
777
- fn=update_viability_realtime,
778
- inputs=[blue_threshold2, masks_state, image_state],
779
- outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_percent_output2, viability_info2]
780
- )
781
-
782
- # Slider changes update viability in real-time
783
- blue_threshold2.change(
784
- fn=update_viability_realtime,
785
- inputs=[blue_threshold2, masks_state, image_state],
786
- outputs=[viability_overlay2, live_count_output2, dead_count_output2, viability_percent_output2, viability_info2]
787
- )
788
-
789
- with gr.Tab("Tab 3"):
790
- gr.Markdown("Run segmentation")
791
-
792
- with gr.Row():
793
- with gr.Column():
794
- img_input3 = gr.Image(
795
- type="pil",
796
- label="Microscopy image",
797
- image_mode="RGB",
798
- height=512
799
- )
800
-
801
- model_dropdown3 = gr.Dropdown(
802
- choices=list(MODEL_OPTIONS.keys()),
803
- label="Select Model",
804
- value="Hemocytometer Model"
805
- )
806
-
807
- min_size_slider3 = gr.Slider(
808
- minimum=0,
809
- maximum=500,
810
- value=0,
811
- step=10,
812
- label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
813
- )
814
-
815
- max_size_slider3 = gr.Slider(
816
- minimum=0,
817
- maximum=10000,
818
- value=10000,
819
- step=10,
820
- label="Maximum Cell Size (pixels)",
821
- )
822
-
823
- # Stereological counting option
824
- gr.Markdown("### Stereological Counting")
825
- use_stereology_checkbox3 = gr.Checkbox(
826
- label="Enable Stereological Counting",
827
- value=False,
828
- info="Use unbiased stereological rules for cell counting"
829
- )
830
-
831
- # Stereological controls (initially hidden)
832
- with gr.Group(visible=False) as stereology_controls3:
833
- gr.Markdown("""
834
- **Stereological Counting Rules:**
835
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
836
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
837
- - This provides unbiased counting for quantification
838
- """)
839
-
840
- exclusion_preview3 = gr.Image(
841
- type="pil",
842
- label="Exclusion Zone Preview (Red = Excluded)",
843
- height=500
844
- )
845
-
846
- left_exclusion_slider3 = gr.Slider(
847
- minimum=0,
848
- maximum=50,
849
- value=10,
850
- step=1,
851
- label="Left Exclusion Width (%)",
852
- info="Width of left exclusion zone"
853
- )
854
-
855
- top_exclusion_slider3 = gr.Slider(
856
- minimum=0,
857
- maximum=50,
858
- value=10,
859
- step=1,
860
- label="Top Exclusion Width (%)",
861
- info="Width of top exclusion zone"
862
- )
863
-
864
- segment_btn3 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
865
-
866
- with gr.Column():
867
- cell_count_output3 = gr.Number(label="Total Cells Detected", precision=0)
868
- confluency_output3 = gr.Number(label="Confluency (%)", precision=1)
869
- overlay_output3 = gr.Image(type="pil", label="Segmentation Result")
870
- info_output3 = gr.Textbox(label="Processing Info", lines=4)
871
-
872
- # Viability Assessment Section
873
- with gr.Group(visible=False) as viability_section3:
874
- gr.Markdown("### Viability Assessment (Trypan Blue)")
875
- gr.Markdown("Adjust the threshold to classify cells as live (green) or dead (red).")
876
-
877
- with gr.Row():
878
- with gr.Column():
879
- blue_threshold3 = gr.Slider(
880
- minimum=0,
881
- maximum=100,
882
- value=25,
883
- step=1,
884
- label="Blue Threshold (%)",
885
- info="Higher values = more selective for blue cells"
886
- )
887
-
888
- with gr.Column():
889
- live_count_output3 = gr.Number(label="Live Cells (Green)", precision=0)
890
- dead_count_output3 = gr.Number(label="Dead Cells (Red)", precision=0)
891
-
892
- viability_overlay3 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
893
- viability_percent_output3 = gr.Number(label="Viability (%)", precision=1)
894
- viability_info3 = gr.Textbox(label="Analysis Results", lines=5)
895
-
896
- # Event handlers
897
-
898
- # Toggle stereological controls visibility
899
- use_stereology_checkbox3.change(
900
- fn=toggle_stereological_mode,
901
- inputs=[use_stereology_checkbox3],
902
- outputs=[stereology_controls3]
903
- )
904
-
905
- # Update exclusion preview when image is uploaded or sliders change
906
- img_input3.change(
907
- fn=update_exclusion_preview,
908
- inputs=[img_input3, left_exclusion_slider3, top_exclusion_slider3],
909
- outputs=[exclusion_preview3]
910
- )
911
-
912
- left_exclusion_slider3.change(
913
- fn=update_exclusion_preview,
914
- inputs=[img_input3, left_exclusion_slider3, top_exclusion_slider3],
915
- outputs=[exclusion_preview3]
916
- )
917
-
918
- top_exclusion_slider3.change(
919
- fn=update_exclusion_preview,
920
- inputs=[img_input3, left_exclusion_slider3, top_exclusion_slider3],
921
- outputs=[exclusion_preview3]
922
- )
923
-
924
- # Run segmentation
925
- segment_btn3.click(
926
- fn=run_segmentation,
927
- inputs=[
928
- img_input3,
929
- model_dropdown3,
930
- min_size_slider3,
931
- max_size_slider3,
932
- use_stereology_checkbox3,
933
- left_exclusion_slider3,
934
- top_exclusion_slider3
935
- ],
936
- outputs=[
937
- cell_count_output3,
938
- overlay_output3,
939
- info_output3,
940
- viability_section3,
941
- masks_state,
942
- image_state,
943
- confluency_output3,
944
- min_size_slider3
945
- ]
946
- ).then( # Chain the initial viability assessment after segmentation
947
- fn=update_viability_realtime,
948
- inputs=[blue_threshold3, masks_state, image_state],
949
- outputs=[viability_overlay3, live_count_output3, dead_count_output3, viability_percent_output3, viability_info3]
950
- )
951
-
952
- # Slider changes update viability in real-time
953
- blue_threshold3.change(
954
- fn=update_viability_realtime,
955
- inputs=[blue_threshold3, masks_state, image_state],
956
- outputs=[viability_overlay3, live_count_output3, dead_count_output3, viability_percent_output3, viability_info3]
957
- )
958
-
959
- with gr.Tab("Tab 4"):
960
- gr.Markdown("Run segmentation")
961
-
962
- with gr.Row():
963
- with gr.Column():
964
- img_input4 = gr.Image(
965
- type="pil",
966
- label="Microscopy image",
967
- image_mode="RGB",
968
- height=512
969
- )
970
-
971
- model_dropdown4 = gr.Dropdown(
972
- choices=list(MODEL_OPTIONS.keys()),
973
- label="Select Model",
974
- value="Hemocytometer Model"
975
- )
976
-
977
- min_size_slider4 = gr.Slider(
978
- minimum=0,
979
- maximum=500,
980
- value=0,
981
- step=10,
982
- label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
983
- )
984
-
985
- max_size_slider4 = gr.Slider(
986
- minimum=0,
987
- maximum=10000,
988
- value=10000,
989
- step=10,
990
- label="Maximum Cell Size (pixels)",
991
- )
992
-
993
- # Stereological counting option
994
- gr.Markdown("### Stereological Counting")
995
- use_stereology_checkbox4 = gr.Checkbox(
996
- label="Enable Stereological Counting",
997
- value=False,
998
- info="Use unbiased stereological rules for cell counting"
999
- )
1000
-
1001
- # Stereological controls (initially hidden)
1002
- with gr.Group(visible=False) as stereology_controls4:
1003
- gr.Markdown("""
1004
- **Stereological Counting Rules:**
1005
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
1006
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
1007
- - This provides unbiased counting for quantification
1008
- """)
1009
-
1010
- exclusion_preview4 = gr.Image(
1011
- type="pil",
1012
- label="Exclusion Zone Preview (Red = Excluded)",
1013
- height=500
1014
- )
1015
-
1016
- left_exclusion_slider4 = gr.Slider(
1017
- minimum=0,
1018
- maximum=50,
1019
- value=10,
1020
- step=1,
1021
- label="Left Exclusion Width (%)",
1022
- info="Width of left exclusion zone"
1023
- )
1024
-
1025
- top_exclusion_slider4 = gr.Slider(
1026
- minimum=0,
1027
- maximum=50,
1028
- value=10,
1029
- step=1,
1030
- label="Top Exclusion Width (%)",
1031
- info="Width of top exclusion zone"
1032
- )
1033
-
1034
- segment_btn4 = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
1035
-
1036
- with gr.Column():
1037
- cell_count_output4 = gr.Number(label="Total Cells Detected", precision=0)
1038
- confluency_output4 = gr.Number(label="Confluency (%)", precision=1)
1039
- overlay_output4 = gr.Image(type="pil", label="Segmentation Result")
1040
- info_output4 = gr.Textbox(label="Processing Info", lines=4)
1041
-
1042
- # Viability Assessment Section
1043
- with gr.Group(visible=False) as viability_section4:
1044
- gr.Markdown("### Viability Assessment (Trypan Blue)")
1045
- gr.Markdown("Adjust the threshold to classify cells as live (green) or dead (red).")
1046
-
1047
- with gr.Row():
1048
- with gr.Column():
1049
- blue_threshold4 = gr.Slider(
1050
- minimum=0,
1051
- maximum=100,
1052
- value=25,
1053
- step=1,
1054
- label="Blue Threshold (%)",
1055
- info="Higher values = more selective for blue cells"
1056
- )
1057
-
1058
- with gr.Column():
1059
- live_count_output4 = gr.Number(label="Live Cells (Green)", precision=0)
1060
- dead_count_output4 = gr.Number(label="Dead Cells (Red)", precision=0)
1061
-
1062
- viability_overlay4 = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
1063
- viability_percent_output4 = gr.Number(label="Viability (%)", precision=1)
1064
- viability_info4 = gr.Textbox(label="Analysis Results", lines=5)
1065
-
1066
- # Event handlers
1067
-
1068
- # Toggle stereological controls visibility
1069
- use_stereology_checkbox4.change(
1070
- fn=toggle_stereological_mode,
1071
- inputs=[use_stereology_checkbox4],
1072
- outputs=[stereology_controls4]
1073
- )
1074
-
1075
- # Update exclusion preview when image is uploaded or sliders change
1076
- img_input4.change(
1077
- fn=update_exclusion_preview,
1078
- inputs=[img_input4, left_exclusion_slider4, top_exclusion_slider4],
1079
- outputs=[exclusion_preview4]
1080
- )
1081
-
1082
- left_exclusion_slider4.change(
1083
- fn=update_exclusion_preview,
1084
- inputs=[img_input4, left_exclusion_slider4, top_exclusion_slider4],
1085
- outputs=[exclusion_preview4]
1086
- )
1087
-
1088
- top_exclusion_slider4.change(
1089
- fn=update_exclusion_preview,
1090
- inputs=[img_input4, left_exclusion_slider4, top_exclusion_slider4],
1091
- outputs=[exclusion_preview4]
1092
- )
1093
-
1094
- # Run segmentation
1095
- segment_btn4.click(
1096
- fn=run_segmentation,
1097
- inputs=[
1098
- img_input4,
1099
- model_dropdown4,
1100
- min_size_slider4,
1101
- max_size_slider4,
1102
- use_stereology_checkbox4,
1103
- left_exclusion_slider4,
1104
- top_exclusion_slider4
1105
- ],
1106
- outputs=[
1107
- cell_count_output4,
1108
- overlay_output4,
1109
- info_output4,
1110
- viability_section4,
1111
- masks_state,
1112
- image_state,
1113
- confluency_output4,
1114
- min_size_slider4
1115
- ]
1116
- ).then( # Chain the initial viability assessment after segmentation
1117
- fn=update_viability_realtime,
1118
- inputs=[blue_threshold4, masks_state, image_state],
1119
- outputs=[viability_overlay4, live_count_output4, dead_count_output4, viability_percent_output4, viability_info4]
1120
- )
1121
-
1122
- # Slider changes update viability in real-time
1123
- blue_threshold4.change(
1124
- fn=update_viability_realtime,
1125
- inputs=[blue_threshold4, masks_state, image_state],
1126
- outputs=[viability_overlay4, live_count_output4, dead_count_output4, viability_percent_output4, viability_info4]
1127
- )
1128
-
1129
- # Instructions
1130
- with gr.Accordion("Instructions", open=False):
1131
- gr.Markdown("""
1132
- ### How to use:
1133
-
1134
- 1. **Upload and Segment**:
1135
- - Upload your microscopy image.
1136
- - Select a Cellpose model (e.g., "Hemocytometer Model" for suspension culture).
1137
- - **(Optional)** Enable Stereological Counting for unbiased quantification.
1138
- - Click "Run Segmentation".
1139
-
1140
- 2. **Stereological Counting** (Optional):
1141
- - Check "Enable Stereological Counting" to use unbiased counting rules.
1142
- - Adjust the Left and Top exclusion zone widths using the sliders.
1143
- - Preview shows excluded areas in red.
1144
- - **Counting Rules**:
1145
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
1146
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
1147
- - This ensures unbiased, systematic counting
1148
-
1149
- 3. **Analysis Results**:
1150
- - **Cell Count**: Total number of detected cells (after exclusions if using stereology)
1151
- - **Confluency**: Percentage of image area covered by cells
1152
-
1153
- 4. **Real-time Viability Assessment (Trypan Blue)**:
1154
- - After segmentation, the viability section will become visible.
1155
- - Adjust the **"Blue Threshold (%)"** slider in real-time.
1156
- - **Lower values (10-20%)** are more sensitive.
1157
- - **Higher values (30-50%)** are more selective.
1158
- - Green cells = Live, Red cells = Dead.
1159
-
1160
- 5. **Interpreting Results**:
1161
- - The app displays total, live, and dead cell counts, viability percentage, and confluency.
1162
- - If stereological counting is enabled, excluded cells are noted in the processing info.
1163
- """)
1164
-
1165
- if __name__ == "__main__":
1166
- demo.launch()