EnginDev commited on
Commit
c96ea8b
·
verified ·
1 Parent(s): 86e0411

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -68
app.py CHANGED
@@ -60,70 +60,152 @@ def refine_mask(mask, kernel_size=5):
60
  mask_refined = cv2.morphologyEx(mask_closed, cv2.MORPH_OPEN, kernel)
61
  return mask_refined > 0
62
 
63
- def segment_automatic(image, quality="high"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  if image is None:
65
  return None, {"error": "Kein Bild hochgeladen"}
66
 
67
  try:
68
- print(f"🔄 Starting segmentation (quality: {quality})...")
69
  model, processor = load_model()
70
 
71
  image_pil, image_np = prepare_image(image)
72
  h, w = image_np.shape[:2]
73
 
 
74
  center_x, center_y = w // 2, h // 2
 
 
 
 
 
 
 
75
 
76
- inputs = processor(
77
- image_pil,
78
- input_points=[[[center_x, center_y]]],
79
- input_labels=[[1]],
80
- return_tensors="pt"
81
- ).to(device)
82
-
83
- print("🧠 Running inference...")
84
- with torch.no_grad():
85
- outputs = model(**inputs, multimask_output=True)
86
-
87
- masks = processor.image_processor.post_process_masks(
88
- outputs.pred_masks.cpu(),
89
- inputs["original_sizes"].cpu(),
90
- inputs["reshaped_input_sizes"].cpu()
91
- )[0]
92
 
93
- scores = outputs.iou_scores.cpu().numpy()
94
- if scores.ndim > 1:
95
- scores = scores.flatten()
96
 
97
- print(f"✅ Got {len(scores)} masks with scores: {scores}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- best_idx = np.argmax(scores)
100
- best_score = scores[best_idx]
101
 
102
- if masks.ndim == 4:
103
- best_mask = masks[0, best_idx].numpy()
 
104
  else:
105
- best_mask = masks[best_idx].numpy()
 
 
106
 
 
107
  if quality == "high":
108
- best_mask_refined = refine_mask(best_mask, kernel_size=7)
109
- else:
110
- best_mask_refined = best_mask > 0
111
 
 
112
  overlay = image_np.copy()
113
- color = np.array([255, 50, 150])
114
 
115
- mask_float = best_mask_refined.astype(float)
116
  if quality == "high":
117
- mask_float = cv2.GaussianBlur(mask_float, (5, 5), 0)
118
 
119
  for c in range(3):
120
  overlay[:, :, c] = (
121
- overlay[:, :, c] * (1 - mask_float * 0.6) +
122
- color[c] * mask_float * 0.6
123
  )
124
 
 
125
  contours, _ = cv2.findContours(
126
- best_mask_refined.astype(np.uint8),
127
  cv2.RETR_EXTERNAL,
128
  cv2.CHAIN_APPROX_SIMPLE
129
  )
@@ -131,18 +213,20 @@ def segment_automatic(image, quality="high"):
131
 
132
  metadata = {
133
  "success": True,
134
- "mode": "automatic",
135
  "quality": quality,
136
- "score": float(best_score),
137
- "all_scores": scores.tolist(),
 
 
138
  "image_size": [w, h],
139
- "mask_area": int(np.sum(best_mask_refined)),
140
- "mask_percentage": float(np.sum(best_mask_refined) / (h * w) * 100),
141
  "num_contours": len(contours),
142
  "device": device
143
  }
144
 
145
- print("✅ Segmentation complete!")
146
  return Image.fromarray(overlay.astype(np.uint8)), metadata
147
 
148
  except Exception as e:
@@ -151,6 +235,7 @@ def segment_automatic(image, quality="high"):
151
  return image, {"error": str(e)}
152
 
153
  def segment_multi_dense(image, density="medium"):
 
154
  if image is None:
155
  return None, {"error": "Kein Bild"}
156
 
@@ -251,47 +336,73 @@ def segment_multi_dense(image, density="medium"):
251
  return image, {"error": str(e)}
252
 
253
  # Gradio Interface
254
- demo = gr.Blocks(title="SAM2 v2.0", theme=gr.themes.Soft())
255
 
256
  with demo:
257
- gr.Markdown("# 🎨 SAM2 Segmentierung v2.0")
258
- gr.Markdown("### Verbesserte Qualität mit Large Model")
259
 
260
- with gr.Tab("🤖 Automatisch"):
 
 
 
261
  with gr.Row():
262
  with gr.Column():
263
- input_auto = gr.Image(type="pil", label="Bild hochladen")
 
264
  quality_radio = gr.Radio(
265
  choices=["high", "fast"],
266
  value="high",
267
  label="Qualität"
268
  )
269
- btn_auto = gr.Button("Segmentieren", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  with gr.Column():
272
- output_auto = gr.Image(label="Ergebnis")
273
- json_auto = gr.JSON(label="Details")
274
 
275
  btn_auto.click(
276
  fn=segment_automatic,
277
- inputs=[input_auto, quality_radio],
278
  outputs=[output_auto, json_auto]
279
  )
280
 
281
  with gr.Tab("🎯 Multi-Region"):
 
 
282
  with gr.Row():
283
  with gr.Column():
284
- input_multi = gr.Image(type="pil", label="Bild hochladen")
285
  density_radio = gr.Radio(
286
  choices=["high", "medium", "low"],
287
  value="medium",
288
  label="Punkt-Dichte"
289
  )
290
- btn_multi = gr.Button("Alle Bereiche", variant="primary", size="lg")
 
 
 
 
 
 
 
291
 
292
  with gr.Column():
293
- output_multi = gr.Image(label="Ergebnis")
294
- json_multi = gr.JSON(label="Details")
295
 
296
  btn_multi.click(
297
  fn=segment_multi_dense,
@@ -299,28 +410,35 @@ with demo:
299
  outputs=[output_multi, json_multi]
300
  )
301
 
302
- with gr.Tab("📡 API"):
303
- gr.Markdown("### API Endpoint")
304
- gr.Markdown("URL: https://EnginDev-Boostly.hf.space/api/predict")
 
 
 
 
305
  gr.Markdown("### JavaScript Beispiel")
306
  gr.Code('''
307
- fetch('https://EnginDev-Boostly.hf.space/api/predict', {
308
  method: 'POST',
309
  headers: {'Content-Type': 'application/json'},
310
  body: JSON.stringify({
311
- data: [imageBase64, "high"],
312
- fn_index: 0
313
  })
314
- })
315
- .then(r => r.json())
316
- .then(result => console.log(result.data));
 
 
317
  ''', language="javascript")
318
 
319
  gr.Markdown("### Parameter")
320
- gr.Markdown("- fn_index: 0 = Automatisch, 1 = Multi-Region")
321
- gr.Markdown("- quality: high oder fast")
322
- gr.Markdown("- density: high, medium, low")
 
323
 
324
  if __name__ == "__main__":
325
- print("🌐 Launching v2.0...")
326
  demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
60
  mask_refined = cv2.morphologyEx(mask_closed, cv2.MORPH_OPEN, kernel)
61
  return mask_refined > 0
62
 
63
+ def merge_nearby_masks(masks, scores, distance_threshold=100):
64
+ """
65
+ Kombiniert Masken die nah beieinander sind
66
+ → Fisch + Flosse werden EINS!
67
+ """
68
+ if len(masks) == 0:
69
+ return np.zeros_like(masks[0]) if len(masks) > 0 else None
70
+
71
+ # Finde Zentren aller Masken
72
+ centers = []
73
+ for mask in masks:
74
+ y_coords, x_coords = np.where(mask)
75
+ if len(x_coords) > 0:
76
+ center_x = int(np.mean(x_coords))
77
+ center_y = int(np.mean(y_coords))
78
+ centers.append((center_x, center_y))
79
+ else:
80
+ centers.append(None)
81
+
82
+ # Gruppiere Masken die nah beieinander sind
83
+ combined = np.zeros_like(masks[0], dtype=bool)
84
+
85
+ for i, mask in enumerate(masks):
86
+ if centers[i] is None:
87
+ continue
88
+
89
+ # Checke ob diese Maske nah an bereits kombinierten Masken ist
90
+ if np.sum(combined) > 0:
91
+ # Distanz zwischen Masken
92
+ combined_coords = np.argwhere(combined)
93
+ mask_coords = np.argwhere(mask)
94
+
95
+ if len(combined_coords) > 0 and len(mask_coords) > 0:
96
+ # Minimale Distanz zwischen den Masken
97
+ min_dist = float('inf')
98
+ for mc in mask_coords[::10]: # Sample alle 10 Punkte
99
+ for cc in combined_coords[::10]:
100
+ dist = np.linalg.norm(mc - cc)
101
+ min_dist = min(min_dist, dist)
102
+
103
+ # Wenn nah genug: kombinieren!
104
+ if min_dist < distance_threshold:
105
+ combined = combined | mask
106
+ print(f" ✅ Merged mask {i} (dist: {min_dist:.1f})")
107
+ else:
108
+ # Erste Maske
109
+ combined = mask
110
+
111
+ return combined
112
+
113
+ def segment_automatic(image, quality="high", merge_parts=True):
114
+ """
115
+ Automatische Segmentierung mit Multi-Point Detection
116
+ merge_parts=True → Kombiniert getrennte Teile wie Flossen!
117
+ """
118
  if image is None:
119
  return None, {"error": "Kein Bild hochgeladen"}
120
 
121
  try:
122
+ print(f"🔄 Starting segmentation (quality: {quality}, merge: {merge_parts})...")
123
  model, processor = load_model()
124
 
125
  image_pil, image_np = prepare_image(image)
126
  h, w = image_np.shape[:2]
127
 
128
+ # MEHRERE Punkte für bessere Abdeckung!
129
  center_x, center_y = w // 2, h // 2
130
+ points = [
131
+ [center_x, center_y], # Mitte
132
+ [center_x, center_y - h//4], # Oben
133
+ [center_x, center_y + h//4], # Unten
134
+ [center_x - w//4, center_y], # Links
135
+ [center_x + w//4, center_y] # Rechts
136
+ ]
137
 
138
+ all_masks = []
139
+ all_scores = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
+ print(f"🎯 Using {len(points)} detection points...")
 
 
142
 
143
+ for idx, point in enumerate(points):
144
+ inputs = processor(
145
+ image_pil,
146
+ input_points=[[point]],
147
+ input_labels=[[1]],
148
+ return_tensors="pt"
149
+ ).to(device)
150
+
151
+ with torch.no_grad():
152
+ outputs = model(**inputs, multimask_output=True)
153
+
154
+ masks = processor.image_processor.post_process_masks(
155
+ outputs.pred_masks.cpu(),
156
+ inputs["original_sizes"].cpu(),
157
+ inputs["reshaped_input_sizes"].cpu()
158
+ )[0]
159
+
160
+ scores = outputs.iou_scores.cpu().numpy()
161
+ if scores.ndim > 1:
162
+ scores = scores.flatten()
163
+
164
+ # Beste Maske von diesem Punkt
165
+ best_idx = np.argmax(scores)
166
+
167
+ if masks.ndim == 4:
168
+ mask = masks[0, best_idx].numpy()
169
+ else:
170
+ mask = masks[best_idx].numpy()
171
+
172
+ # Nur gute Masken
173
+ if scores[best_idx] > 0.6:
174
+ all_masks.append(mask > 0)
175
+ all_scores.append(scores[best_idx])
176
+ print(f" ✅ Point {idx}: Score {scores[best_idx]:.3f}")
177
 
178
+ print(f"✅ Got {len(all_masks)} quality masks")
 
179
 
180
+ # MERGE: Kombiniere nahe Masken
181
+ if merge_parts and len(all_masks) > 0:
182
+ combined_mask = merge_nearby_masks(all_masks, all_scores, distance_threshold=100)
183
  else:
184
+ combined_mask = np.zeros((h, w), dtype=bool)
185
+ for mask in all_masks:
186
+ combined_mask = combined_mask | mask
187
 
188
+ # Refinement
189
  if quality == "high":
190
+ combined_mask = refine_mask(combined_mask, kernel_size=9)
 
 
191
 
192
+ # Overlay
193
  overlay = image_np.copy()
194
+ color = np.array([255, 80, 180]) # Rosa/Pink
195
 
196
+ mask_float = combined_mask.astype(float)
197
  if quality == "high":
198
+ mask_float = cv2.GaussianBlur(mask_float, (7, 7), 0)
199
 
200
  for c in range(3):
201
  overlay[:, :, c] = (
202
+ overlay[:, :, c] * (1 - mask_float * 0.65) +
203
+ color[c] * mask_float * 0.65
204
  )
205
 
206
+ # Kontur
207
  contours, _ = cv2.findContours(
208
+ combined_mask.astype(np.uint8),
209
  cv2.RETR_EXTERNAL,
210
  cv2.CHAIN_APPROX_SIMPLE
211
  )
 
213
 
214
  metadata = {
215
  "success": True,
216
+ "mode": "automatic_plus" if merge_parts else "automatic",
217
  "quality": quality,
218
+ "detection_points": len(points),
219
+ "masks_found": len(all_masks),
220
+ "avg_score": float(np.mean(all_scores)) if all_scores else 0,
221
+ "all_scores": [float(s) for s in all_scores],
222
  "image_size": [w, h],
223
+ "mask_area": int(np.sum(combined_mask)),
224
+ "mask_percentage": float(np.sum(combined_mask) / (h * w) * 100),
225
  "num_contours": len(contours),
226
  "device": device
227
  }
228
 
229
+ print(f"✅ Segmentation complete! {len(contours)} contours")
230
  return Image.fromarray(overlay.astype(np.uint8)), metadata
231
 
232
  except Exception as e:
 
235
  return image, {"error": str(e)}
236
 
237
  def segment_multi_dense(image, density="medium"):
238
+ """Multi-Object mit dichterem Grid"""
239
  if image is None:
240
  return None, {"error": "Kein Bild"}
241
 
 
336
  return image, {"error": str(e)}
337
 
338
  # Gradio Interface
339
+ demo = gr.Blocks(title="SAM2 Boostly", theme=gr.themes.Soft())
340
 
341
  with demo:
342
+ gr.Markdown("# 🎨 SAM2 Segmentierung - Boostly Edition")
343
+ gr.Markdown("### Zero-Shot Object Segmentation powered by Meta SAM")
344
 
345
+ with gr.Tab("🤖 Automatisch PLUS"):
346
+ gr.Markdown("**Intelligent Multi-Point Detection** - Erkennt und kombiniert alle Objektteile!")
347
+ gr.Markdown("✨ Perfekt für: Fische mit Flossen, Menschen mit Armen/Beinen, komplexe Objekte")
348
+
349
  with gr.Row():
350
  with gr.Column():
351
+ input_auto = gr.Image(type="pil", label="📸 Bild hochladen")
352
+
353
  quality_radio = gr.Radio(
354
  choices=["high", "fast"],
355
  value="high",
356
  label="Qualität"
357
  )
358
+
359
+ merge_checkbox = gr.Checkbox(
360
+ value=True,
361
+ label="✅ Teile zusammenfügen (Empfohlen!)"
362
+ )
363
+
364
+ btn_auto = gr.Button("🚀 Segmentieren", variant="primary", size="lg")
365
+
366
+ gr.Markdown("""
367
+ **So funktioniert's:**
368
+ - 🎯 5 Erkennungspunkte (Mitte + 4 Richtungen)
369
+ - 🔗 Intelligentes Merging naher Bereiche
370
+ - 🎨 Fisch + Flosse = EIN zusammenhängendes Objekt!
371
+ """)
372
 
373
  with gr.Column():
374
+ output_auto = gr.Image(label="✨ Ergebnis")
375
+ json_auto = gr.JSON(label="📊 Details")
376
 
377
  btn_auto.click(
378
  fn=segment_automatic,
379
+ inputs=[input_auto, quality_radio, merge_checkbox],
380
  outputs=[output_auto, json_auto]
381
  )
382
 
383
  with gr.Tab("🎯 Multi-Region"):
384
+ gr.Markdown("**Grid-basierte Segmentierung** - Für mehrere separate Objekte")
385
+
386
  with gr.Row():
387
  with gr.Column():
388
+ input_multi = gr.Image(type="pil", label="📸 Bild hochladen")
389
  density_radio = gr.Radio(
390
  choices=["high", "medium", "low"],
391
  value="medium",
392
  label="Punkt-Dichte"
393
  )
394
+ btn_multi = gr.Button("🎯 Alle Bereiche", variant="primary", size="lg")
395
+
396
+ gr.Markdown("""
397
+ **Grid-Größen:**
398
+ - 🔥 High: 5x5 = 25 Punkte
399
+ - ⚡ Medium: 4x4 = 16 Punkte
400
+ - 💨 Low: 3x3 = 9 Punkte
401
+ """)
402
 
403
  with gr.Column():
404
+ output_multi = gr.Image(label="✨ Ergebnis")
405
+ json_multi = gr.JSON(label="📊 Details")
406
 
407
  btn_multi.click(
408
  fn=segment_multi_dense,
 
410
  outputs=[output_multi, json_multi]
411
  )
412
 
413
+ with gr.Tab("📡 API Integration"):
414
+ gr.Markdown("### API Endpoint für Lovable/externe Apps")
415
+ gr.Code(
416
+ "https://EnginDev-Boostly.hf.space/api/predict",
417
+ label="API URL"
418
+ )
419
+
420
  gr.Markdown("### JavaScript Beispiel")
421
  gr.Code('''
422
+ const response = await fetch('https://EnginDev-Boostly.hf.space/api/predict', {
423
  method: 'POST',
424
  headers: {'Content-Type': 'application/json'},
425
  body: JSON.stringify({
426
+ data: [imageBase64, "high", true], // [image, quality, merge]
427
+ fn_index: 0 // 0 = Automatisch PLUS, 1 = Multi-Region
428
  })
429
+ });
430
+
431
+ const result = await response.json();
432
+ const segmentedImage = result.data[0]; // Base64 Image
433
+ const metadata = result.data[1]; // JSON Details
434
  ''', language="javascript")
435
 
436
  gr.Markdown("### Parameter")
437
+ gr.Markdown("- **fn_index**: 0 = Automatisch PLUS, 1 = Multi-Region")
438
+ gr.Markdown("- **quality**: 'high' (präzise) oder 'fast' (schnell)")
439
+ gr.Markdown("- **merge**: true (Teile kombinieren) oder false")
440
+ gr.Markdown("- **density**: 'high', 'medium', 'low' (nur Multi-Region)")
441
 
442
  if __name__ == "__main__":
443
+ print("🌐 Launching Boostly SAM2...")
444
  demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)