LiangLabUMB commited on
Commit
297e92a
·
verified ·
1 Parent(s): a5b41e7

Delete app (2).py

Browse files
Files changed (1) hide show
  1. app (2).py +0 -689
app (2).py DELETED
@@ -1,689 +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
- MAX_SIDE = 1024
22
- MAX_PIXELS = 1024 * 1024
23
-
24
-
25
- def safe_resize(image_np):
26
- h, w = image_np.shape[:2]
27
- total = h * w
28
-
29
- if max(h, w) <= MAX_SIDE and total <= MAX_PIXELS:
30
- return image_np
31
-
32
- # compute scale
33
- scale_side = MAX_SIDE / max(h, w)
34
- scale_pixels = (MAX_PIXELS / total) ** 0.5
35
- scale = min(scale_side, scale_pixels)
36
-
37
- new_w = max(1, int(w * scale))
38
- new_h = max(1, int(h * scale))
39
-
40
- return cv2.resize(image_np, (new_w, new_h), interpolation=cv2.INTER_AREA)
41
-
42
-
43
- def draw_exclusion_overlay(image_np, left_width_pct, top_width_pct):
44
-
45
- h, w = image_np.shape[:2]
46
-
47
- # Convert to PIL
48
- img_pil = Image.fromarray(image_np)
49
- draw = ImageDraw.Draw(img_pil, 'RGBA')
50
-
51
-
52
- left_px = int(w * left_width_pct / 100)
53
- top_px = int(h * top_width_pct / 100)
54
-
55
- # Draw overlays for exclusion zones
56
- if left_px > 0:
57
- draw.rectangle(
58
- [(0, 0), (left_px, h)],
59
- fill=(255, 0, 0, 80)
60
- )
61
- # border line
62
- draw.line([(left_px, 0), (left_px, h)], fill=(255, 0, 0, 255), width=3)
63
-
64
- if top_px > 0:
65
- draw.rectangle(
66
- [(0, 0), (w, top_px)],
67
- fill=(255, 0, 0, 80)
68
- )
69
- # border line
70
- draw.line([(0, top_px), (w, top_px)], fill=(255, 0, 0, 255), width=3)
71
-
72
- return np.array(img_pil)
73
-
74
-
75
- def apply_stereological_exclusion(masks, left_width_pct, top_width_pct):
76
- h, w = masks.shape
77
-
78
- left_px = int(w * left_width_pct / 100)
79
- top_px = int(h * top_width_pct / 100)
80
-
81
- filtered_masks = masks.copy()
82
- cell_ids = np.unique(masks)
83
- cell_ids = cell_ids[cell_ids > 0]
84
-
85
- excluded_cells = []
86
- included_cells = []
87
-
88
- for cell_id in cell_ids:
89
- cell_mask = (masks == cell_id)
90
-
91
- # Get cell boundary coordinates
92
- rows, cols = np.where(cell_mask)
93
-
94
- touches_left = np.any(cols < left_px) if left_px > 0 else False
95
- touches_top = np.any(rows < top_px) if top_px > 0 else False
96
-
97
- if touches_left or touches_top:
98
- filtered_masks[cell_mask] = 0
99
- excluded_cells.append(cell_id)
100
- else:
101
- included_cells.append(cell_id)
102
- unique_ids = np.unique(filtered_masks)
103
- unique_ids = unique_ids[unique_ids > 0]
104
-
105
- renumbered_masks = np.zeros_like(filtered_masks)
106
- for new_id, old_id in enumerate(unique_ids, start=1):
107
- renumbered_masks[filtered_masks == old_id] = new_id
108
-
109
- return renumbered_masks, len(excluded_cells), len(included_cells)
110
-
111
-
112
- def classify_cells_by_blueness(image_np, masks, blue_threshold):
113
-
114
- if len(image_np.shape) == 2:
115
- image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
116
- elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
117
- image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
118
-
119
-
120
- hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)
121
-
122
- # Calculate blueness index for each pixel
123
- hue = hsv[:, :, 0].astype(np.float32)
124
- saturation = hsv[:, :, 1].astype(np.float32)
125
-
126
- # Hue score: peaks around 115 , drops off towards edges
127
- # Handle hue wrap-around for blue detection (100-130 range)
128
- hue_distance = np.minimum(np.abs(hue - 115), 180 - np.abs(hue - 115))
129
- hue_score = np.maximum(0, 1 - hue_distance / 65) # 65 good blue range
130
-
131
- # blueness metric
132
- blueness = hue_score * (saturation / 255.0)
133
-
134
- # Convert threshold from 0-100 to 0-1 scale
135
- threshold = blue_threshold / 100.0
136
-
137
- # Get unique cell IDs
138
- cell_ids = np.unique(masks)
139
- cell_ids = cell_ids[cell_ids > 0]
140
-
141
- dead_cells = []
142
- alive_cells = []
143
-
144
- # Classify each cell
145
- for cell_id in cell_ids:
146
-
147
- cell_mask = (masks == cell_id)
148
- cell_blueness = np.mean(blueness[cell_mask])
149
-
150
- if cell_blueness > threshold:
151
- dead_cells.append(cell_id)
152
- else:
153
- alive_cells.append(cell_id)
154
-
155
- # Create colored overlay
156
- overlay = image_np.copy().astype(np.float32) # Ensure float for blending
157
-
158
- # Color dead cells red, alive cells green
159
- for cell_id in dead_cells:
160
- cell_mask = (masks == cell_id)
161
- overlay[cell_mask] = [255, 0, 0]
162
-
163
- for cell_id in alive_cells:
164
- cell_mask = (masks == cell_id)
165
- overlay[cell_mask] = [0, 255, 0]
166
-
167
- # Blend with original image
168
- alpha = 0.4
169
- final_overlay = (1 - alpha) * image_np.astype(np.float32) + alpha * overlay
170
- final_overlay = np.clip(final_overlay, 0, 255).astype(np.uint8)
171
-
172
- return len(dead_cells), len(alive_cells), final_overlay
173
-
174
-
175
- def measure_confluency(masks, image_np):
176
- tot_pixels = image_np.shape[0] * image_np.shape[1]
177
- cell_pixels = np.count_nonzero(masks)
178
- confluency = cell_pixels / tot_pixels * 100
179
- return confluency
180
-
181
- def filter_mask_by_size(masks, minimum_pixels):
182
- filtered_masks = masks.copy()
183
- cell_ids = np.unique(masks)
184
- cell_ids = cell_ids[cell_ids > 0]
185
-
186
- removed_count = 0
187
-
188
- for cell_id in cell_ids:
189
- cell_mask = (masks == cell_id)
190
- cell_pixels = np.count_nonzero(cell_mask)
191
- if cell_pixels < minimum_pixels:
192
- filtered_masks[cell_mask] = 0
193
- removed_count += 1
194
-
195
- unique_ids = np.unique(filtered_masks)
196
- unique_ids = unique_ids[unique_ids > 0]
197
-
198
- renumbered_masks = np.zeros_like(filtered_masks)
199
- for new_id, old_id in enumerate(unique_ids, start=1):
200
- renumbered_masks[filtered_masks == old_id] = new_id
201
-
202
- return renumbered_masks, removed_count
203
-
204
-
205
- def filter_mask_by_maxsize(masks, maximum_pixels):
206
- filtered_masks = masks.copy()
207
- cell_ids = np.unique(masks)
208
- cell_ids = cell_ids[cell_ids > 0]
209
-
210
- removed_count = 0
211
- for cell_id in cell_ids:
212
- cell_mask = (masks == cell_id)
213
- cell_pixels = np.count_nonzero(cell_mask)
214
- if cell_pixels > maximum_pixels:
215
- filtered_masks[cell_mask] = 0
216
- removed_count += 1
217
-
218
- unique_ids = np.unique(filtered_masks)
219
- unique_ids = unique_ids[unique_ids > 0]
220
-
221
- renumbered_masks = np.zeros_like(filtered_masks)
222
- for new_id, old_id in enumerate(unique_ids, start=1):
223
- renumbered_masks[filtered_masks == old_id] = new_id
224
-
225
- return renumbered_masks, removed_count
226
-
227
-
228
- def rec_min_size(masks, q=25):
229
- ids = np.unique(masks)
230
- ids = ids[ids > 0]
231
- if len(ids) == 0:
232
- return 0
233
- sizes = np.array([np.count_nonzero(masks == cid) for cid in ids])
234
- return int(round(np.percentile(sizes, q)))
235
-
236
-
237
- def toggle_stereological_mode(use_stereology):
238
- """Show/hide stereological controls based on checkbox"""
239
- return gr.update(visible=use_stereology)
240
-
241
-
242
- def update_exclusion_preview(image, left_width, top_width):
243
- """Update the preview image with exclusion zone overlay"""
244
- if image is None:
245
- return None
246
-
247
- image_np = np.array(image)
248
- overlay = draw_exclusion_overlay(image_np, left_width, top_width)
249
- return Image.fromarray(overlay)
250
-
251
-
252
- @spaces.GPU
253
- def run_segmentation(image, model_choice, min_cell_size, max_cell_size,
254
- use_stereology, left_exclusion, top_exclusion):
255
- image_np = np.array(image)
256
- image_np = safe_resize(image_np)
257
-
258
- try:
259
- model_filename = MODEL_OPTIONS[model_choice]
260
- model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
261
-
262
- if model_filename in loaded_models:
263
- model = loaded_models[model_filename]
264
- else:
265
- model = models.CellposeModel(gpu=True, pretrained_model=model_path)
266
- loaded_models[model_filename] = model
267
-
268
- # Process image format to RGB
269
- if len(image_np.shape) == 2:
270
- processed_image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
271
- elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
272
- processed_image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
273
- else:
274
- processed_image_np = image_np
275
-
276
- # Run Cellpose segmentation
277
- masks_raw, flows, styles = model.eval(processed_image_np, diameter=None, channels=[0, 0])
278
-
279
- ids = np.unique(masks_raw)
280
- ids = ids[ids > 0]
281
-
282
- sizes = np.array([np.count_nonzero(masks_raw == cid) for cid in ids])
283
-
284
- print("num_cells:", len(ids))
285
- print("mean:", sizes.mean() if len(sizes) > 0 else 0)
286
- print("median:", np.median(sizes) if len(sizes) > 0 else 0)
287
- print("p90:", np.percentile(sizes, 90) if len(sizes) > 0 else 0)
288
- print("max:", sizes.max() if len(sizes) > 0 else 0)
289
-
290
- # Compute recommendation from RAW masks
291
- recommend_min = rec_min_size(masks_raw)
292
-
293
- # If user sets slider to 0, use the recommendation
294
- min_used = recommend_min if (min_cell_size == 0) else int(min_cell_size)
295
-
296
- # Apply filters
297
- masks = masks_raw.copy()
298
- removed_small = 0
299
- removed_large = 0
300
-
301
- if min_used > 0:
302
- masks, removed_small = filter_mask_by_size(masks, min_used)
303
-
304
- if max_cell_size > 0:
305
- masks, removed_large = filter_mask_by_maxsize(masks, int(max_cell_size))
306
-
307
- # Apply stereological exclusion if enabled
308
- excluded_count = 0
309
- if use_stereology:
310
- masks, excluded_count, included_count = apply_stereological_exclusion(
311
- masks, left_exclusion, top_exclusion
312
- )
313
-
314
- filter_msg = ""
315
- if removed_small:
316
- filter_msg += f"Removed {removed_small} small objects (< {min_used} pixels).\n"
317
- if removed_large:
318
- filter_msg += f"Removed {removed_large} large objects (> {int(max_cell_size)} pixels).\n"
319
- if use_stereology and excluded_count > 0:
320
- filter_msg += f"Stereological exclusion: {excluded_count} cells excluded (touching left/top zones).\n"
321
-
322
- cell_count = len(np.unique(masks)) - 1
323
- confluency = measure_confluency(masks, processed_image_np)
324
-
325
- # Create segmentation overlay (without viability)
326
- segmentation_overlay = processed_image_np.copy().astype(np.float32)
327
- if masks.max() > 0:
328
- np.random.seed(42)
329
- colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
330
- colors[0] = [0, 0, 0]
331
- colored_mask = colors[masks]
332
- alpha = 0.4
333
- segmentation_overlay = (1 - alpha) * segmentation_overlay + alpha * colored_mask
334
- segmentation_overlay = np.clip(segmentation_overlay, 0, 255).astype(np.uint8)
335
-
336
- # Add exclusion zone overlay if stereology is enabled
337
- if use_stereology:
338
- segmentation_overlay = draw_exclusion_overlay(segmentation_overlay, left_exclusion, top_exclusion)
339
-
340
- info_msg = ""
341
- if filter_msg:
342
- info_msg += filter_msg
343
- info_msg += f"Segmentation complete! Found {cell_count} cells.\n"
344
- info_msg += f"Confluency: {confluency:.1f}%\n"
345
- if use_stereology:
346
- info_msg += f"Stereological counting enabled (Left: {left_exclusion}%, Top: {top_exclusion}%)\n"
347
- info_msg += "Now adjust the Blue Threshold for viability assessment."
348
-
349
- return (
350
- cell_count,
351
- Image.fromarray(segmentation_overlay),
352
- info_msg,
353
- gr.update(visible=True),
354
- pack_array(masks),
355
- pack_array(processed_image_np),
356
- confluency,
357
- gr.update(value=recommend_min), # update slider display to recommended
358
- )
359
-
360
- except Exception as e:
361
- import traceback
362
- traceback.print_exc()
363
- return (
364
- 0,
365
- None,
366
- f"Error during segmentation: {str(e)}",
367
- gr.update(visible=False),
368
- None,
369
- None,
370
- 0.0,
371
- gr.update(),
372
- )
373
-
374
-
375
- def update_viability_realtime(blue_threshold, stored_masks, stored_image_np):
376
- # avoid unpacking None (e.g. slider moved before segmentation)
377
- if stored_masks is None or stored_image_np is None:
378
- return None, 0, 0, 0.0, "Please run segmentation first."
379
-
380
- stored_masks = unpack_array(stored_masks)
381
- stored_image_np = unpack_array(stored_image_np)
382
-
383
- try:
384
- dead_count, alive_count, viability_overlay_np = classify_cells_by_blueness(
385
- stored_image_np, stored_masks, blue_threshold
386
- )
387
-
388
- total_count = alive_count + dead_count
389
- viability_percent = (alive_count / total_count * 100) if total_count > 0 else 0.0
390
- confluency = measure_confluency(stored_masks, stored_image_np)
391
-
392
- overlay_image = Image.fromarray(viability_overlay_np)
393
- info_msg = f"Total cells: {total_count}\nLive (green): {alive_count}\nDead (red): {dead_count}\n"
394
- info_msg += f"Viability: {viability_percent:.1f}%\nConfluency: {confluency:.1f}%\nBlue threshold: {blue_threshold}%"
395
-
396
- return overlay_image, alive_count, dead_count, viability_percent, info_msg
397
-
398
- except Exception as e:
399
- return None, 0, 0, 0.0, f"Error updating viability: {str(e)}"
400
-
401
-
402
- def pack_array(arr):
403
- pil = Image.fromarray(arr.astype(np.uint8))
404
- buf = io.BytesIO()
405
- pil.save(buf, format="PNG")
406
- return buf.getvalue()
407
-
408
-
409
- def unpack_array(data):
410
- return np.array(Image.open(io.BytesIO(data)))
411
-
412
-
413
- def save_tab_result(cell_count, confluency, viab_percent):
414
- """Package per-tab results into a dict for Tab 5 summary."""
415
- return {
416
- "cell_count": float(cell_count) if cell_count is not None else None,
417
- "confluency": float(confluency) if confluency is not None else None,
418
- "viab_percent": float(viab_percent) if viab_percent is not None else None,
419
- }
420
-
421
-
422
- def compute_summary(r1, r2, r3, r4):
423
- """Average cell count, confluency, and viability across tabs that have data."""
424
- all_results = [r1, r2, r3, r4]
425
- valid = [(i + 1, r) for i, r in enumerate(all_results)
426
- if r is not None and r.get("cell_count") is not None]
427
-
428
- if not valid:
429
- return (
430
- 0.0, 0.0, 0.0,
431
- "No data yet — run segmentation in at least one tab, then click Refresh Summary."
432
- )
433
-
434
- avg_count = sum(r["cell_count"] for _, r in valid) / len(valid)
435
- avg_conf = sum(r["confluency"] for _, r in valid) / len(valid)
436
- avg_viab = sum(r["viab_percent"] for _, r in valid) / len(valid)
437
-
438
- lines = [f"Tab {tab_num}: {r['cell_count']:.0f} cells | "
439
- f"{r['confluency']:.1f}% confluency | "
440
- f"{r['viab_percent']:.1f}% viability"
441
- for tab_num, r in valid]
442
- lines.append(f"\nAverages ({len(valid)} tab{'s' if len(valid) > 1 else ''}):")
443
- lines.append(f" Cell count: {avg_count:.1f}")
444
- lines.append(f" Confluency: {avg_conf:.1f}%")
445
- lines.append(f" Viability: {avg_viab:.1f}%")
446
-
447
- return avg_count, avg_conf, avg_viab, "\n".join(lines)
448
-
449
-
450
- def build_tab(tab_index, masks_state, image_state, result_state):
451
- with gr.Tab(f"Tab {tab_index}"):
452
- gr.Markdown("Run segmentation")
453
-
454
- with gr.Row():
455
- with gr.Column():
456
- img_input = gr.Image(
457
- type="pil",
458
- label="Microscopy image",
459
- image_mode="RGB",
460
- height=512
461
- )
462
-
463
- model_dropdown = gr.Dropdown(
464
- choices=list(MODEL_OPTIONS.keys()),
465
- label="Select Model",
466
- value="Hemocytometer Model"
467
- )
468
-
469
- min_size_slider = gr.Slider(
470
- minimum=0,
471
- maximum=500,
472
- value=0,
473
- step=10,
474
- label="Minimum Cell Size (pixels). Leave at zero for automated recommendation",
475
- )
476
-
477
- max_size_slider = gr.Slider(
478
- minimum=0,
479
- maximum=10000,
480
- value=10000,
481
- step=10,
482
- label="Maximum Cell Size (pixels)",
483
- )
484
-
485
- gr.Markdown("### Stereological Counting")
486
- use_stereo = gr.Checkbox(
487
- label="Enable Stereological Counting",
488
- value=False,
489
- info="Use unbiased stereological rules for cell counting"
490
- )
491
-
492
- with gr.Group(visible=False) as stereo_controls:
493
- gr.Markdown("""
494
- **Stereological Counting Rules:**
495
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
496
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
497
- - This provides unbiased counting for quantification
498
- """)
499
-
500
- excl_preview = gr.Image(
501
- type="pil",
502
- label="Exclusion Zone Preview (Red = Excluded)",
503
- height=500
504
- )
505
-
506
- left_excl = gr.Slider(
507
- minimum=0,
508
- maximum=50,
509
- value=10,
510
- step=1,
511
- label="Left Exclusion Width (%)",
512
- info="Width of left exclusion zone"
513
- )
514
-
515
- top_excl = gr.Slider(
516
- minimum=0,
517
- maximum=50,
518
- value=10,
519
- step=1,
520
- label="Top Exclusion Width (%)",
521
- info="Width of top exclusion zone"
522
- )
523
-
524
- segment_btn = gr.Button("🔬 Run Segmentation", variant="primary", size="lg")
525
-
526
- with gr.Column():
527
- cell_count_out = gr.Number(label="Total Cells Detected", precision=0)
528
- confluency_out = gr.Number(label="Confluency (%)", precision=1)
529
- overlay_out = gr.Image(type="pil", label="Segmentation Result")
530
- info_out = gr.Textbox(label="Processing Info", lines=4)
531
-
532
- with gr.Group(visible=False) as viability_section:
533
- gr.Markdown("### Viability Assessment (Trypan Blue)")
534
- gr.Markdown("Adjust the threshold to classify cells as live (green) or dead (red).")
535
-
536
- with gr.Row():
537
- with gr.Column():
538
- blue_thresh = gr.Slider(
539
- minimum=0,
540
- maximum=100,
541
- value=25,
542
- step=1,
543
- label="Blue Threshold (%)",
544
- info="Higher values = more selective for blue cells"
545
- )
546
- with gr.Column():
547
- live_count_out = gr.Number(label="Live Cells (Green)", precision=0)
548
- dead_count_out = gr.Number(label="Dead Cells (Red)", precision=0)
549
-
550
- viab_overlay = gr.Image(type="pil", label="Viability Assessment (Green=Live, Red=Dead)")
551
- viab_percent_out = gr.Number(label="Viability (%)", precision=1)
552
- viab_info = gr.Textbox(label="Analysis Results", lines=5)
553
-
554
- # Event handlers
555
-
556
- use_stereo.change(
557
- fn=toggle_stereological_mode,
558
- inputs=[use_stereo],
559
- outputs=[stereo_controls]
560
- )
561
-
562
- img_input.change(
563
- fn=update_exclusion_preview,
564
- inputs=[img_input, left_excl, top_excl],
565
- outputs=[excl_preview]
566
- )
567
- left_excl.change(
568
- fn=update_exclusion_preview,
569
- inputs=[img_input, left_excl, top_excl],
570
- outputs=[excl_preview]
571
- )
572
- top_excl.change(
573
- fn=update_exclusion_preview,
574
- inputs=[img_input, left_excl, top_excl],
575
- outputs=[excl_preview]
576
- )
577
-
578
- segment_btn.click(
579
- fn=run_segmentation,
580
- inputs=[img_input, model_dropdown, min_size_slider, max_size_slider,
581
- use_stereo, left_excl, top_excl],
582
- outputs=[cell_count_out, overlay_out, info_out, viability_section,
583
- masks_state, image_state, confluency_out, min_size_slider]
584
- ).then(
585
- fn=update_viability_realtime,
586
- inputs=[blue_thresh, masks_state, image_state],
587
- outputs=[viab_overlay, live_count_out, dead_count_out, viab_percent_out, viab_info]
588
- ).then(
589
- # Save this tab's results into its dedicated state for Tab 5
590
- fn=save_tab_result,
591
- inputs=[cell_count_out, confluency_out, viab_percent_out],
592
- outputs=[result_state]
593
- )
594
-
595
- blue_thresh.change(
596
- fn=update_viability_realtime,
597
- inputs=[blue_thresh, masks_state, image_state],
598
- outputs=[viab_overlay, live_count_out, dead_count_out, viab_percent_out, viab_info]
599
- ).then(
600
- # Keep result state in sync when threshold is adjusted
601
- fn=save_tab_result,
602
- inputs=[cell_count_out, confluency_out, viab_percent_out],
603
- outputs=[result_state]
604
- )
605
-
606
-
607
-
608
- with gr.Blocks(
609
- title="CellposeCellCounter",
610
- theme=gr.themes.Soft(),
611
- ) as demo:
612
- gr.Markdown("# CellposeCellCounter")
613
- 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.")
614
-
615
- # Shared mask/image state (one pair per tab so tabs don't clobber each other)
616
- masks_states = [gr.State(value=None) for _ in range(4)]
617
- image_states = [gr.State(value=None) for _ in range(4)]
618
- result_states = [gr.State(value=None) for _ in range(4)]
619
-
620
- # Build Tabs 1–4 with a loop
621
- for i in range(4):
622
- build_tab(i + 1, masks_states[i], image_states[i], result_states[i])
623
-
624
- # Tab 5
625
- with gr.Tab("Tab 5 — Summary"):
626
- gr.Markdown("## Average Results Across All Tabs")
627
- gr.Markdown(
628
- "Run segmentation (and optionally adjust the blue threshold) in one or more tabs, "
629
- "then click **Refresh Summary** to see the averages."
630
- )
631
-
632
- refresh_btn = gr.Button("Refresh Summary", variant="primary", size="lg")
633
-
634
- with gr.Row():
635
- avg_count_out = gr.Number(label="Avg Cell Count", precision=1)
636
- avg_conf_out = gr.Number(label="Avg Confluency (%)", precision=1)
637
- avg_viab_out = gr.Number(label="Avg Viability (%)", precision=1)
638
-
639
- summary_box = gr.Textbox(label="Per-Tab Breakdown", lines=10)
640
-
641
- refresh_btn.click(
642
- fn=compute_summary,
643
- inputs=result_states, # list of 4 gr.State components
644
- outputs=[avg_count_out, avg_conf_out, avg_viab_out, summary_box]
645
- )
646
-
647
- # Instructions
648
- with gr.Accordion("Instructions", open=False):
649
- gr.Markdown("""
650
- ### How to use:
651
-
652
- 1. **Upload and Segment**:
653
- - Upload your microscopy image.
654
- - Select a Cellpose model (e.g., "Hemocytometer Model" for suspension culture).
655
- - **(Optional)** Enable Stereological Counting for unbiased quantification.
656
- - Click "Run Segmentation".
657
-
658
- 2. **Stereological Counting** (Optional):
659
- - Check "Enable Stereological Counting" to use unbiased counting rules.
660
- - Adjust the Left and Top exclusion zone widths using the sliders.
661
- - Preview shows excluded areas in red.
662
- - **Counting Rules**:
663
- - Cells touching LEFT or TOP exclusion zones are EXCLUDED
664
- - Cells touching RIGHT or BOTTOM edges are INCLUDED
665
- - This ensures unbiased, systematic counting
666
-
667
- 3. **Analysis Results**:
668
- - **Cell Count**: Total number of detected cells (after exclusions if using stereology)
669
- - **Confluency**: Percentage of image area covered by cells
670
-
671
- 4. **Real-time Viability Assessment (Trypan Blue)**:
672
- - After segmentation, the viability section will become visible.
673
- - Adjust the **"Blue Threshold (%)"** slider in real-time.
674
- - **Lower values (10-20%)** are more sensitive.
675
- - **Higher values (30-50%)** are more selective.
676
- - Green cells = Live, Red cells = Dead.
677
-
678
- 5. **Summary (Tab 5)**:
679
- - After running segmentation in one or more tabs, switch to **Tab 5**.
680
- - Click **Refresh Summary** to compute averages across all completed tabs.
681
- - Displays average cell count, confluency, and viability, plus a per-tab breakdown.
682
-
683
- 6. **Interpreting Results**:
684
- - The app displays total, live, and dead cell counts, viability percentage, and confluency.
685
- - If stereological counting is enabled, excluded cells are noted in the processing info.
686
- """)
687
-
688
- if __name__ == "__main__":
689
- demo.launch()