LiangLabUMB commited on
Commit
69eddc2
·
verified ·
1 Parent(s): 965469b

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -458
app.py DELETED
@@ -1,458 +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
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
- def debug_editor_data(editor_data):
21
- """Debug function to understand the structure of ImageEditor data"""
22
- if editor_data is None:
23
- return "No data provided"
24
-
25
- info = f"Type: {type(editor_data)}\n"
26
-
27
- if isinstance(editor_data, dict):
28
- info += f"Keys: {list(editor_data.keys())}\n"
29
- for key, value in editor_data.items():
30
- info += f" {key}: {type(value)}\n"
31
- if key == 'layers' and value:
32
- info += f" Layers count: {len(value)}\n"
33
- for i, layer in enumerate(value):
34
- info += f" Layer {i}: {type(layer)}\n"
35
- if hasattr(layer, 'size'):
36
- info += f" Size: {layer.size}\n"
37
- info += f" Mode: {layer.mode}\n"
38
- else:
39
- if hasattr(editor_data, 'size'):
40
- info += f"Size: {editor_data.size}\n"
41
- info += f"Mode: {editor_data.mode}\n"
42
-
43
- return info
44
-
45
- def extract_region_from_editor(editor_data):
46
- """Extract the selected region from ImageEditor data"""
47
- if editor_data is None:
48
- return None, None
49
-
50
- # ImageEditor can return different formats depending on version
51
- # Let's handle the most common cases
52
-
53
- if isinstance(editor_data, dict):
54
- # Case 1: Dictionary with 'background' and 'layers'
55
- background = editor_data.get('background')
56
- layers = editor_data.get('layers', [])
57
-
58
- if background is None:
59
- return None, None
60
-
61
- # Convert background to numpy array
62
- background_np = np.array(background)
63
-
64
- # If there are drawn layers, try to extract selection
65
- if layers and len(layers) > 0:
66
- # Get the first layer
67
- selection_layer = layers[0]
68
-
69
- # Convert to numpy array
70
- selection_np = np.array(selection_layer)
71
-
72
- # Find non-transparent/non-black pixels as selection
73
- if len(selection_np.shape) == 3:
74
- # For RGB, look for non-black pixels
75
- if selection_np.shape[2] == 4: # RGBA
76
- # Use alpha channel
77
- mask = selection_np[:, :, 3] > 0
78
- else: # RGB
79
- # Use non-black pixels
80
- mask = np.any(selection_np > 0, axis=2)
81
- else:
82
- # Grayscale
83
- mask = selection_np > 0
84
-
85
- # Find bounding box of the selection
86
- coords = np.where(mask)
87
- if len(coords[0]) > 0:
88
- y_min, y_max = coords[0].min(), coords[0].max()
89
- x_min, x_max = coords[1].min(), coords[1].max()
90
-
91
- # Add some padding to ensure we don't get tiny regions
92
- pad = 5
93
- h, w = background_np.shape[:2]
94
- y_min = max(0, y_min - pad)
95
- y_max = min(h, y_max + pad)
96
- x_min = max(0, x_min - pad)
97
- x_max = min(w, x_max + pad)
98
-
99
- # Extract the region
100
- region = background_np[y_min:y_max+1, x_min:x_max+1]
101
- return region, (x_min, y_min, x_max, y_max)
102
-
103
- # If no selection, return the full image
104
- return background_np, None
105
-
106
- else:
107
- # Case 2: Direct PIL Image
108
- if hasattr(editor_data, 'size'): # Check if it's a PIL Image
109
- image_np = np.array(editor_data)
110
- return image_np, None
111
- else:
112
- return None, None
113
-
114
- def crop_image_with_coords(image_np, coords):
115
- """Crop image using provided coordinates"""
116
- if coords is None:
117
- return image_np
118
-
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
133
- """
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)
154
- cell_ids = cell_ids[cell_ids > 0] # Remove background (0)
155
-
156
- dead_cells = []
157
- alive_cells = []
158
-
159
- # Classify each cell
160
- for cell_id in cell_ids:
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)
172
-
173
- # Create colored overlay
174
- overlay = image_np.copy()
175
-
176
- # Color dead cells red, alive cells green
177
- for cell_id in dead_cells:
178
- cell_mask = (masks == cell_id)
179
- overlay[cell_mask] = [255, 0, 0] # Red for dead
180
-
181
- for cell_id in alive_cells:
182
- cell_mask = (masks == cell_id)
183
- overlay[cell_mask] = [0, 255, 0] # Green for alive
184
-
185
- # Blend with original image
186
- alpha = 0.4
187
- overlay = (1 - alpha) * image_np + alpha * overlay
188
- overlay = np.clip(overlay, 0, 255).astype(np.uint8)
189
-
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
199
- debug_info = debug_editor_data(editor_data)
200
-
201
- # Load the model
202
- model_filename = MODEL_OPTIONS[model_choice]
203
- model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
204
-
205
- if model_filename in loaded_models:
206
- model = loaded_models[model_filename]
207
- else:
208
- model = models.CellposeModel(gpu=True, pretrained_model=model_path)
209
- loaded_models[model_filename] = model
210
-
211
- # Extract region from editor
212
- region_np, region_coords = extract_region_from_editor(editor_data)
213
-
214
- if region_np is None:
215
- return 0, 0, 0, 0.0, None, f"No image provided. Debug info:\n{debug_info}"
216
-
217
- # If manual crop coordinates are provided, use them instead
218
- if crop_coords and crop_coords.strip():
219
- try:
220
- coords = [int(x.strip()) for x in crop_coords.split(',')]
221
- if len(coords) == 4:
222
- x_min, y_min, x_max, y_max = coords
223
- # Ensure coordinates are within image bounds
224
- h, w = region_np.shape[:2]
225
- x_min = max(0, min(x_min, w-1))
226
- y_min = max(0, min(y_min, h-1))
227
- x_max = max(x_min+1, min(x_max, w))
228
- y_max = max(y_min+1, min(y_max, h))
229
-
230
- region_np = region_np[y_min:y_max, x_min:x_max]
231
- region_coords = (x_min, y_min, x_max, y_max)
232
- except ValueError:
233
- pass # Invalid coordinates, continue with current region
234
-
235
- # If grayscale, convert to RGB
236
- if len(region_np.shape) == 2:
237
- region_np = cv2.cvtColor(region_np, cv2.COLOR_GRAY2RGB)
238
- elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
239
- # Handle RGBA images
240
- region_np = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
241
-
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
251
- viability_percent = (alive_count / total_count * 100) if total_count > 0 else 0.0
252
-
253
- # Convert result to PIL Image for output
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
-
261
- return total_count, alive_count, dead_count, viability_percent, overlay_image, info_msg
262
-
263
- except Exception as e:
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"
271
-
272
- try:
273
- # Load the model
274
- model_filename = MODEL_OPTIONS[model_choice]
275
- model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
276
-
277
- if model_filename in loaded_models:
278
- model = loaded_models[model_filename]
279
- else:
280
- model = models.CellposeModel(gpu=True, pretrained_model=model_path)
281
- loaded_models[model_filename] = model
282
-
283
- # Convert PIL Image to numpy array
284
- image_np = np.array(image)
285
-
286
- # Apply crop if coordinates are provided
287
- if crop_coords and crop_coords.strip():
288
- try:
289
- coords = [int(x.strip()) for x in crop_coords.split(',')]
290
- if len(coords) == 4:
291
- x_min, y_min, x_max, y_max = coords
292
- # Ensure coordinates are within image bounds
293
- h, w = image_np.shape[:2]
294
- x_min = max(0, min(x_min, w-1))
295
- y_min = max(0, min(y_min, h-1))
296
- x_max = max(x_min+1, min(x_max, w))
297
- y_max = max(y_min+1, min(y_max, h))
298
-
299
- image_np = image_np[y_min:y_max, x_min:x_max]
300
- except ValueError:
301
- pass # Invalid coordinates, use full image
302
-
303
- # Process image format
304
- if len(image_np.shape) == 2:
305
- image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
306
- elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
307
- image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
308
-
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
318
- viability_percent = (alive_count / total_count * 100) if total_count > 0 else 0.0
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
-
326
- return total_count, alive_count, dead_count, viability_percent, overlay_image, info_msg
327
-
328
- except Exception as e:
329
- return 0, 0, 0, 0.0, None, f"Error occurred: {str(e)}"
330
-
331
- # Create the Gradio interface with tabs for different input methods
332
- with gr.Blocks(title="Cell Viability Counter with Trypan Blue Analysis") as demo:
333
- gr.Markdown("# Cell Viability Counter with Trypan Blue Analysis")
334
- gr.Markdown("Upload a trypan blue stained microscopy image and analyze cell viability. Dead cells appear blue, alive cells are unstained.")
335
-
336
- with gr.Tab("Image Editor (Draw Selection)"):
337
- gr.Markdown("Use the drawing tools to select a region of the image for segmentation and viability analysis.")
338
-
339
- with gr.Row():
340
- with gr.Column():
341
- image_editor = gr.ImageEditor(
342
- label="Draw selection on image",
343
- type="pil",
344
- brush=gr.Brush(colors=["#ff0000"], color_mode="fixed", default_size=20),
345
- eraser=gr.Eraser(default_size=20)
346
- )
347
-
348
- model_dropdown1 = gr.Dropdown(
349
- choices=list(MODEL_OPTIONS.keys()),
350
- label="Select Model",
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
-
364
- with gr.Column():
365
- with gr.Row():
366
- total_count_output1 = gr.Number(label="Total Cells")
367
- alive_count_output1 = gr.Number(label="Alive Cells")
368
- dead_count_output1 = gr.Number(label="Dead Cells")
369
-
370
- viability_output1 = gr.Number(label="Viability (%)", precision=1)
371
- overlay_output1 = gr.Image(type="pil", label="Viability Analysis (Green=Alive, Red=Dead)")
372
- info_output1 = gr.Textbox(label="Analysis Results")
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
-
380
- with gr.Tab("Manual Coordinates"):
381
- gr.Markdown("Upload an image and specify coordinates manually (format: x_min,y_min,x_max,y_max)")
382
-
383
- with gr.Row():
384
- with gr.Column():
385
- image_input = gr.Image(type="pil", label="Trypan Blue Stained Image")
386
-
387
- model_dropdown2 = gr.Dropdown(
388
- choices=list(MODEL_OPTIONS.keys()),
389
- label="Select Model",
390
- value="Hemocytometer Model"
391
- )
392
-
393
- coord_input = gr.Textbox(
394
- label="Crop Coordinates (optional)",
395
- placeholder="e.g., 100,100,400,400",
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
-
409
- with gr.Column():
410
- with gr.Row():
411
- total_count_output2 = gr.Number(label="Total Cells")
412
- alive_count_output2 = gr.Number(label="Alive Cells")
413
- dead_count_output2 = gr.Number(label="Dead Cells")
414
-
415
- viability_output2 = gr.Number(label="Viability (%)", precision=1)
416
- overlay_output2 = gr.Image(type="pil", label="Viability Analysis (Green=Alive, Red=Dead)")
417
- info_output2 = gr.Textbox(label="Analysis Results")
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
-
425
- # Add usage instructions
426
- with gr.Tab("Usage Instructions"):
427
- gr.Markdown("""
428
- ## How to Use This Tool
429
-
430
- ### 1. Image Preparation
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
448
- - **Red overlay**: Dead cells
449
- - **Viability %**: (Alive cells / Total cells) × 100
450
-
451
- ### 5. Tips
452
- - Use consistent lighting and staining protocols for best results
453
- - Adjust thresholds if cells are misclassified
454
- - The tool works best with clear contrast between stained and unstained cells
455
- """)
456
-
457
- if __name__ == "__main__":
458
- demo.launch()