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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -103
app.py CHANGED
@@ -17,53 +17,99 @@ MODEL_OPTIONS = {
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"""
@@ -83,83 +129,90 @@ def segment_and_count(editor_data, model_choice, crop_coords=None):
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
@@ -293,4 +346,3 @@ with gr.Blocks(title="Cell Counter with Region Selection") as demo:
293
  if __name__ == "__main__":
294
  demo.launch()
295
 
296
-
 
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"""
 
129
  model_choice: Selected model for segmentation
130
  crop_coords: Optional manual crop coordinates as "x_min,y_min,x_max,y_max"
131
  """
132
+ try:
133
+ # Debug info
134
+ debug_info = debug_editor_data(editor_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ # Load the model
137
+ model_filename = MODEL_OPTIONS[model_choice]
138
+ model_path = hf_hub_download(repo_id=HF_REPO_ID, filename=model_filename)
139
 
140
+ if model_filename in loaded_models:
141
+ model = loaded_models[model_filename]
142
+ else:
143
+ model = models.CellposeModel(gpu=True, pretrained_model=model_path)
144
+ loaded_models[model_filename] = model
145
+
146
+ # Extract region from editor
147
+ region_np, region_coords = extract_region_from_editor(editor_data)
148
+
149
+ if region_np is None:
150
+ return 0, None, f"No image provided. Debug info:\n{debug_info}"
151
+
152
+ # If manual crop coordinates are provided, use them instead
153
+ if crop_coords and crop_coords.strip():
154
+ try:
155
+ coords = [int(x.strip()) for x in crop_coords.split(',')]
156
+ if len(coords) == 4:
157
+ x_min, y_min, x_max, y_max = coords
158
+ # Ensure coordinates are within image bounds
159
+ h, w = region_np.shape[:2]
160
+ x_min = max(0, min(x_min, w-1))
161
+ y_min = max(0, min(y_min, h-1))
162
+ x_max = max(x_min+1, min(x_max, w))
163
+ y_max = max(y_min+1, min(y_max, h))
164
+
165
+ region_np = region_np[y_min:y_max, x_min:x_max]
166
+ region_coords = (x_min, y_min, x_max, y_max)
167
+ except ValueError:
168
+ pass # Invalid coordinates, continue with current region
169
+
170
+ # If grayscale, convert to RGB
171
+ if len(region_np.shape) == 2:
172
+ region_np = cv2.cvtColor(region_np, cv2.COLOR_GRAY2RGB)
173
+ elif len(region_np.shape) == 3 and region_np.shape[2] == 4:
174
+ # Handle RGBA images
175
+ region_np = cv2.cvtColor(region_np, cv2.COLOR_RGBA2RGB)
176
+
177
+ # Run Cellpose on the selected region
178
+ masks, flows, styles = model.eval(region_np, diameter=None, channels=[0, 0])
179
+
180
+ # Count unique cells
181
+ cell_count = len(np.unique(masks)) - 1 # subtract 1 for background
182
+
183
+ # Create better overlay visualization
184
+ overlay = region_np.copy().astype(np.float32)
185
+
186
+ # Create colored mask overlay
187
+ if masks.max() > 0:
188
+ # Generate random colors for each cell
189
+ np.random.seed(42) # For reproducible colors
190
+ colors = np.random.randint(0, 255, size=(masks.max() + 1, 3))
191
+ colors[0] = [0, 0, 0] # Background stays black
192
+
193
+ # Create colored overlay
194
+ colored_mask = colors[masks]
195
+
196
+ # Blend with original image
197
+ alpha = 0.4
198
+ overlay = (1 - alpha) * overlay + alpha * colored_mask
199
+
200
+ # Ensure values are in valid range and convert to uint8
201
+ overlay = np.clip(overlay, 0, 255).astype(np.uint8)
202
+
203
+ # Convert result to PIL Image for output
204
+ overlay_image = Image.fromarray(overlay)
205
+
206
+ # Create info message
207
+ if region_coords:
208
+ info_msg = f"Processed region: {region_coords[0]},{region_coords[1]} to {region_coords[2]},{region_coords[3]}\nRegion size: {region_np.shape}\nDebug: {debug_info}"
209
+ else:
210
+ info_msg = f"Processed entire image\nImage size: {region_np.shape}\nDebug: {debug_info}"
211
+
212
+ return cell_count, overlay_image, info_msg
213
+
214
+ except Exception as e:
215
+ return 0, None, f"Error occurred: {str(e)}\nDebug info:\n{debug_info if 'debug_info' in locals() else 'No debug info'}"
216
 
217
  # Alternative function for simple image input with coordinate textbox
218
  @spaces.GPU
 
346
  if __name__ == "__main__":
347
  demo.launch()
348