LiangLabUMB commited on
Commit
375f53e
·
verified ·
1 Parent(s): d88781b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -54
app.py CHANGED
@@ -3,11 +3,13 @@ import spaces
3
  from cellpose import models
4
  import numpy as np
5
  import cv2
 
 
6
  from PIL import Image
 
7
  from huggingface_hub import hf_hub_download
8
 
9
  HF_REPO_ID = "myang4218/cellposemodel"
10
-
11
  MODEL_OPTIONS = {
12
  "Hemocytometer Model": "hemocytometermodel.npy",
13
  "General Model": "generalmodel.npy"
@@ -15,88 +17,278 @@ MODEL_OPTIONS = {
15
 
16
  loaded_models = {}
17
 
18
- @spaces.GPU
19
- def segment_and_count(edited_input, model_choice):
20
- if edited_input is None:
21
- return 0, None
22
-
23
- # Handle Gradio ImageEditor output
24
- if isinstance(edited_input, dict) and "image" in edited_input:
25
- image = edited_input["image"]
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # Optional: apply cropping manually if edit info exists
28
- edit_info = edited_input.get("edit", {})
29
- crop_box = edit_info.get("crop")
30
- if crop_box:
31
- x = int(crop_box["x"])
32
- y = int(crop_box["y"])
33
- w = int(crop_box["width"])
34
- h = int(crop_box["height"])
35
- image = image.crop((x, y, x + w, y + h))
36
- else:
37
- image = edited_input # fallback, already a PIL Image
38
-
39
- # Convert to NumPy
40
- image_np = np.array(image)
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- # Validate shape
43
- if image_np.size == 0 or len(image_np.shape) < 2:
44
- return 0, None
 
 
 
 
45
 
46
- # Load model
 
 
 
 
 
 
 
 
 
 
47
  model_filename = MODEL_OPTIONS[model_choice]
48
  model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
49
-
50
  if model_filename in loaded_models:
51
  model = loaded_models[model_filename]
52
  else:
53
  model = models.CellposeModel(gpu=True, pretrained_model=model_path)
54
  loaded_models[model_filename] = model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- # Ensure image is RGB
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  if len(image_np.shape) == 2:
58
  image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
59
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
60
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
61
-
62
  # Run Cellpose
63
  masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
64
-
65
- # Count cells
66
  cell_count = len(np.unique(masks)) - 1
67
-
68
- # Overlay visualization
69
  overlay = image_np.copy().astype(np.float32)
 
70
  if masks.max() > 0:
71
  np.random.seed(42)
72
  colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
73
  colors[0] = [0, 0, 0]
 
74
  colored_mask = colors[masks]
75
  alpha = 0.4
76
  overlay = (1 - alpha) * overlay + alpha * colored_mask
77
-
78
  overlay = np.clip(overlay, 0, 255).astype(np.uint8)
79
  overlay_image = Image.fromarray(overlay)
 
 
80
 
81
- return cell_count, overlay_image
82
-
83
-
84
-
85
- # Gradio UI
86
- with gr.Blocks() as demo:
87
- gr.Markdown("## 🧪 Cell Counter with Cellpose + ImageEditor")
88
- gr.Markdown("Upload a microscopy image, draw/crop a region using the editor, then select a model to count cells in that region.")
89
-
90
- with gr.Row():
91
- image_editor = gr.ImageEditor(label="Draw or Crop Region", type="pil")
92
- model_selector = gr.Dropdown(choices=list(MODEL_OPTIONS.keys()), value="Hemocytometer Model", label="Select Model")
93
-
94
- with gr.Row():
95
- count_output = gr.Number(label="Number of Cells")
96
- overlay_output = gr.Image(label="Segmented Overlay")
97
-
98
- image_editor.change(fn=segment_and_count, inputs=[image_editor, model_selector], outputs=[count_output, overlay_output])
99
- model_selector.change(fn=segment_and_count, inputs=[image_editor, model_selector], outputs=[count_output, overlay_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  if __name__ == "__main__":
102
  demo.launch()
 
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"
 
17
 
18
  loaded_models = {}
19
 
20
+ def extract_region_from_editor(editor_data):
21
+ """Extract the selected region from ImageEditor data"""
22
+ if editor_data is None:
23
+ return None, None
24
+
25
+ # Get the background image and layers
26
+ background = editor_data.get('background')
27
+ layers = editor_data.get('layers', [])
28
+
29
+ if background is None:
30
+ return None, None
31
+
32
+ # Convert background to numpy array
33
+ background_np = np.array(background)
34
+
35
+ # If there are layers (selections), process them
36
+ if layers:
37
+ # For simplicity, we'll use the first layer as the selection mask
38
+ # In a more complex implementation, you might want to combine multiple layers
39
+ selection_layer = layers[0]
40
 
41
+ # The layer contains the selection information
42
+ # This is a simplified approach - you might need to adjust based on your specific needs
43
+ selection_image = selection_layer.get('image')
44
+ if selection_image is not None:
45
+ # Convert selection to mask
46
+ selection_np = np.array(selection_image)
47
+
48
+ # Create a binary mask from the selection
49
+ if len(selection_np.shape) == 3:
50
+ # If it's an RGB image, convert to grayscale for mask
51
+ mask = cv2.cvtColor(selection_np, cv2.COLOR_RGB2GRAY)
52
+ else:
53
+ mask = selection_np
54
+
55
+ # Find bounding box of the selection
56
+ coords = np.where(mask > 0)
57
+ if len(coords[0]) > 0:
58
+ y_min, y_max = coords[0].min(), coords[0].max()
59
+ x_min, x_max = coords[1].min(), coords[1].max()
60
+
61
+ # Extract the region
62
+ region = background_np[y_min:y_max+1, x_min:x_max+1]
63
+ return region, (x_min, y_min, x_max, y_max)
64
+
65
+ # If no selection, return the full image
66
+ return background_np, None
67
 
68
+ def crop_image_with_coords(image_np, coords):
69
+ """Crop image using provided coordinates"""
70
+ if coords is None:
71
+ return image_np
72
+
73
+ x_min, y_min, x_max, y_max = coords
74
+ return image_np[y_min:y_max+1, x_min:x_max+1]
75
 
76
+ @spaces.GPU
77
+ def segment_and_count(editor_data, model_choice, crop_coords=None):
78
+ """
79
+ Segment and count cells in the selected region
80
+
81
+ Args:
82
+ editor_data: Data from ImageEditor component
83
+ model_choice: Selected model for segmentation
84
+ crop_coords: Optional manual crop coordinates as "x_min,y_min,x_max,y_max"
85
+ """
86
+ # Load the model
87
  model_filename = MODEL_OPTIONS[model_choice]
88
  model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
89
+
90
  if model_filename in loaded_models:
91
  model = loaded_models[model_filename]
92
  else:
93
  model = models.CellposeModel(gpu=True, pretrained_model=model_path)
94
  loaded_models[model_filename] = model
95
+
96
+ # Extract region from editor
97
+ region_np, region_coords = extract_region_from_editor(editor_data)
98
+
99
+ if region_np is None:
100
+ return 0, None, "No image provided"
101
+
102
+ # If manual crop coordinates are provided, use them instead
103
+ if crop_coords and crop_coords.strip():
104
+ try:
105
+ coords = [int(x.strip()) for x in crop_coords.split(',')]
106
+ if len(coords) == 4:
107
+ x_min, y_min, x_max, y_max = coords
108
+ # Ensure coordinates are within image bounds
109
+ h, w = region_np.shape[:2]
110
+ x_min = max(0, min(x_min, w-1))
111
+ y_min = max(0, min(y_min, h-1))
112
+ x_max = max(x_min+1, min(x_max, w))
113
+ y_max = max(y_min+1, min(y_max, h))
114
+
115
+ region_np = region_np[y_min:y_max, x_min:x_max]
116
+ region_coords = (x_min, y_min, x_max, y_max)
117
+ except ValueError:
118
+ pass # Invalid coordinates, continue with current region
119
+
120
+ # If grayscale, convert to RGB
121
+ if len(region_np.shape) == 2:
122
+ region_np = cv2.cvtColor(region_np, cv2.COLOR_GRAY2RGB)
123
+ elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
124
+ # Handle RGBA images
125
+ region_np = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
126
+
127
+ # Run Cellpose on the selected region
128
+ masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
129
+
130
+ # Count unique cells
131
+ cell_count = len(np.unique(masks)) - 1 # subtract 1 for background
132
+
133
+ # Create better overlay visualization
134
+ overlay = region_np.copy().astype(np.float32)
135
+
136
+ # Create colored mask overlay
137
+ if masks.max() > 0:
138
+ # Generate random colors for each cell
139
+ np.random.seed(42) # For reproducible colors
140
+ colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
141
+ colors[0] = [0, 0, 0] # Background stays black
142
+
143
+ # Create colored overlay
144
+ colored_mask = colors[masks]
145
+
146
+ # Blend with original image
147
+ alpha = 0.4
148
+ overlay = (1 - alpha) * overlay + alpha * colored_mask
149
+
150
+ # Ensure values are in valid range and convert to uint8
151
+ overlay = np.clip(overlay, 0, 255).astype(np.uint8)
152
+
153
+ # Convert result to PIL Image for output
154
+ overlay_image = Image.fromarray(overlay)
155
+
156
+ # Create info message
157
+ if region_coords:
158
+ info_msg = f"Processed region: {region_coords[0]},{region_coords[1]} to {region_coords[2]},{region_coords[3]}"
159
+ else:
160
+ info_msg = "Processed entire image"
161
+
162
+ return cell_count, overlay_image, info_msg
163
 
164
+ # Alternative function for simple image input with coordinate textbox
165
+ @spaces.GPU
166
+ def segment_with_coords(image, model_choice, crop_coords):
167
+ """Segment using regular image input with manual coordinate specification"""
168
+ if image is None:
169
+ return 0, None, "No image provided"
170
+
171
+ # Load the model
172
+ model_filename = MODEL_OPTIONS[model_choice]
173
+ model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
174
+
175
+ if model_filename in loaded_models:
176
+ model = loaded_models[model_filename]
177
+ else:
178
+ model = models.CellposeModel(gpu=True, pretrained_model=model_path)
179
+ loaded_models[model_filename] = model
180
+
181
+ # Convert PIL Image to numpy array
182
+ image_np = np.array(image)
183
+
184
+ # Apply crop if coordinates are provided
185
+ if crop_coords and crop_coords.strip():
186
+ try:
187
+ coords = [int(x.strip()) for x in crop_coords.split(',')]
188
+ if len(coords) == 4:
189
+ x_min, y_min, x_max, y_max = coords
190
+ # Ensure coordinates are within image bounds
191
+ h, w = image_np.shape[:2]
192
+ x_min = max(0, min(x_min, w-1))
193
+ y_min = max(0, min(y_min, h-1))
194
+ x_max = max(x_min+1, min(x_max, w))
195
+ y_max = max(y_min+1, min(y_max, h))
196
+
197
+ image_np = image_np[y_min:y_max, x_min:x_max]
198
+ except ValueError:
199
+ pass # Invalid coordinates, use full image
200
+
201
+ # Process image format
202
  if len(image_np.shape) == 2:
203
  image_np = cv2.cvtColor(image_np, cv2.COLOR_GRAY2RGB)
204
  elif len(image_np.shape) == 3 and image_np.shape[2] == 4:
205
  image_np = cv2.cvtColor(image_np, cv2.COLOR_RGBA2RGB)
206
+
207
  # Run Cellpose
208
  masks, flows, styles = model.eval(image_np, diameter=None, channels=[0, 0])
209
+
210
+ # Count unique cells
211
  cell_count = len(np.unique(masks)) - 1
212
+
213
+ # Create overlay
214
  overlay = image_np.copy().astype(np.float32)
215
+
216
  if masks.max() > 0:
217
  np.random.seed(42)
218
  colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
219
  colors[0] = [0, 0, 0]
220
+
221
  colored_mask = colors[masks]
222
  alpha = 0.4
223
  overlay = (1 - alpha) * overlay + alpha * colored_mask
224
+
225
  overlay = np.clip(overlay, 0, 255).astype(np.uint8)
226
  overlay_image = Image.fromarray(overlay)
227
+
228
+ return cell_count, overlay_image, f"Processed with coordinates: {crop_coords}" if crop_coords else "Processed entire image"
229
 
230
+ # Create the Gradio interface with tabs for different input methods
231
+ with gr.Blocks(title="Cell Counter with Region Selection") as demo:
232
+ gr.Markdown("# Cell Counter with Cellpose - Region Selection")
233
+ gr.Markdown("Upload a microscopy image and select a region to count cells using Cellpose segmentation.")
234
+
235
+ with gr.Tab("Image Editor (Draw Selection)"):
236
+ gr.Markdown("Use the drawing tools to select a region of the image for segmentation.")
237
+
238
+ with gr.Row():
239
+ with gr.Column():
240
+ image_editor = gr.ImageEditor(
241
+ label="Draw selection on image",
242
+ type="pil",
243
+ brush=gr.Brush(colors=["#ff0000"], color_mode="fixed", default_size=20),
244
+ eraser=gr.Eraser(default_size=20)
245
+ )
246
+ model_dropdown1 = gr.Dropdown(
247
+ choices=list(MODEL_OPTIONS.keys()),
248
+ label="Select Model",
249
+ value="Hemocytometer Model"
250
+ )
251
+ segment_btn1 = gr.Button("Segment Selected Region", variant="primary")
252
+
253
+ with gr.Column():
254
+ cell_count_output1 = gr.Number(label="Number of Cells")
255
+ overlay_output1 = gr.Image(type="pil", label="Segmented Overlay")
256
+ info_output1 = gr.Textbox(label="Processing Info")
257
+
258
+ segment_btn1.click(
259
+ fn=segment_and_count,
260
+ inputs=[image_editor, model_dropdown1],
261
+ outputs=[cell_count_output1, overlay_output1, info_output1]
262
+ )
263
+
264
+ with gr.Tab("Manual Coordinates"):
265
+ gr.Markdown("Upload an image and specify coordinates manually (format: x_min,y_min,x_max,y_max)")
266
+
267
+ with gr.Row():
268
+ with gr.Column():
269
+ image_input = gr.Image(type="pil", label="Microscopy Image")
270
+ model_dropdown2 = gr.Dropdown(
271
+ choices=list(MODEL_OPTIONS.keys()),
272
+ label="Select Model",
273
+ value="Hemocytometer Model"
274
+ )
275
+ coord_input = gr.Textbox(
276
+ label="Crop Coordinates (optional)",
277
+ placeholder="e.g., 100,100,400,400",
278
+ info="Format: x_min,y_min,x_max,y_max"
279
+ )
280
+ segment_btn2 = gr.Button("Segment Region", variant="primary")
281
+
282
+ with gr.Column():
283
+ cell_count_output2 = gr.Number(label="Number of Cells")
284
+ overlay_output2 = gr.Image(type="pil", label="Segmented Overlay")
285
+ info_output2 = gr.Textbox(label="Processing Info")
286
+
287
+ segment_btn2.click(
288
+ fn=segment_with_coords,
289
+ inputs=[image_input, model_dropdown2, coord_input],
290
+ outputs=[cell_count_output2, overlay_output2, info_output2]
291
+ )
292
 
293
  if __name__ == "__main__":
294
  demo.launch()