EnginDev commited on
Commit
30285ef
·
verified ·
1 Parent(s): 7a68824

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -402
app.py CHANGED
@@ -1,447 +1,161 @@
1
- import gradio as gr
2
- import torch
3
  import numpy as np
4
- from PIL import Image
5
  import cv2
6
- import io
7
- import time
8
- import traceback
9
-
10
- print("🚀 Starting SAM2 Boostly - Stable CPU-friendly Edition")
11
-
12
- device = "cuda" if torch.cuda.is_available() else "cpu"
13
- print(f"📱 Using device: {device}")
14
 
15
- model = None
16
- processor = None
 
 
 
17
 
18
- # ---- Helper: robustes post_process_masks, kompatibel mit Transformers-Versionen ----
19
- def safe_post_process_masks(processor, outputs, inputs):
20
  """
21
- Versucht mehrere Varianten von post_process_masks aufzurufen.
22
- Liefert eine Liste von mask-arrays passend zur originalen Größe.
23
  """
 
 
 
24
  try:
25
- # Neue API (manche Transformers-Versionen)
26
- masks = processor.post_process_masks(
27
- outputs=outputs,
28
- original_sizes=inputs["original_sizes"].cpu(),
29
- reshaped_input_sizes=inputs["reshaped_input_sizes"].cpu()
30
  )
31
- return masks
32
- except TypeError:
33
- try:
34
- # Alternative: processor.image_processor.post_process_masks(pred_masks, original_sizes, reshaped_input_sizes)
35
- pred = outputs.pred_masks.cpu()
36
- masks = processor.image_processor.post_process_masks(
37
- pred,
38
- inputs["original_sizes"].cpu(),
39
- inputs["reshaped_input_sizes"].cpu()
40
- )
41
- return masks
42
- except Exception:
43
- # Fallback: einfache Verarbeitung - nehme pred_masks (resize zur originalgröße)
44
- try:
45
- pred = outputs.pred_masks.cpu().numpy()
46
- # pred shape handling: try common shapes
47
- # pred: [batch, num_masks, H, W] or [num_masks, H, W]
48
- if pred.ndim == 4:
49
- batch_masks = []
50
- for b in range(pred.shape[0]):
51
- mask_list = []
52
- for m in range(pred.shape[1]):
53
- mask = pred[b, m]
54
- mask_list.append(mask)
55
- batch_masks.append(mask_list)
56
- # Convert to expected structure: list of masks for first image
57
- # We will wrap them similar to new API: [[mask1_resized, ...]]
58
- resized = []
59
- orig_size = inputs["original_sizes"][0].cpu().tolist()
60
- for mask in batch_masks[0]:
61
- mask_img = Image.fromarray((mask * 255).astype(np.uint8))
62
- mask_img = mask_img.resize((orig_size[1], orig_size[0]), Image.Resampling.NEAREST)
63
- resized.append(np.array(mask_img) > 127)
64
- return [ [resized] ]
65
- elif pred.ndim == 3:
66
- # single image, multiple masks
67
- orig_size = inputs["original_sizes"][0].cpu().tolist()
68
- resized = []
69
- for m in range(pred.shape[0]):
70
- mask = pred[m]
71
- mask_img = Image.fromarray((mask * 255).astype(np.uint8))
72
- mask_img = mask_img.resize((orig_size[1], orig_size[0]), Image.Resampling.NEAREST)
73
- resized.append(np.array(mask_img) > 127)
74
- return [ [resized] ]
75
- except Exception as e:
76
- print("⚠️ safe_post_process_masks fallback failed:", e)
77
- traceback.print_exc()
78
- return None
79
-
80
- # ---- Modell laden (einmal) ----
81
- def load_model():
82
- global model, processor
83
- if model is None:
84
- print("📦 Loading SAM model (base recommended for CPU)...")
85
  try:
86
- from transformers import SamModel, SamProcessor
87
- model_name = "facebook/sam-vit-base" # DEFAULT -> base
88
- processor = SamProcessor.from_pretrained(model_name)
89
- model = SamModel.from_pretrained(model_name)
90
- model.to(device)
91
- print(f"✅ Model loaded: {model_name}")
92
- except Exception as e:
93
- print("❌ Error loading transformers SAM-model:", e)
94
- traceback.print_exc()
95
- raise e
96
- return model, processor
97
-
98
- # ---- Bild vorbereiten ----
99
- def prepare_image(image, max_size=1024):
100
- if isinstance(image, np.ndarray):
101
- image_pil = Image.fromarray(image)
102
- else:
103
- image_pil = image
104
-
105
- if image_pil.mode != 'RGB':
106
- image_pil = image_pil.convert('RGB')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- image_np = np.array(image_pil)
109
- h, w = image_np.shape[:2]
110
-
111
- if max(h, w) > max_size:
112
- scale = max_size / max(h, w)
113
- new_h, new_w = int(h * scale), int(w * scale)
114
- image_pil = image_pil.resize((new_w, new_h), Image.Resampling.LANCZOS)
115
- image_np = np.array(image_pil)
116
-
117
- return image_pil, image_np
118
-
119
- # ---- Masken-Raffinement ----
120
- def refine_mask(mask, kernel_size=5):
121
- mask_uint8 = (mask > 0).astype(np.uint8) * 255
122
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size))
123
- mask_closed = cv2.morphologyEx(mask_uint8, cv2.MORPH_CLOSE, kernel)
124
- mask_refined = cv2.morphologyEx(mask_closed, cv2.MORPH_OPEN, kernel)
125
- return mask_refined > 0
126
-
127
- # ---- SMART Automatic segmentation (einzelobjekt) ----
128
  def segment_automatic(image, quality="high", filter_largest=True):
129
  if image is None:
130
  return None, {"error": "Kein Bild hochgeladen"}
131
-
132
- start = time.time()
133
  try:
134
  model, processor = load_model()
135
  image_pil, image_np = prepare_image(image)
136
  h, w = image_np.shape[:2]
137
- center_x, center_y = w // 2, h // 2
138
-
139
- # Device-based multimask decision: only use multimask on GPU
140
- use_multimask = True if device == "cuda" else False
141
 
 
142
  inputs = processor(
143
  image_pil,
144
- input_points=[[[center_x, center_y]]],
145
  input_labels=[[1]],
146
  return_tensors="pt"
147
- ).to(device)
 
148
 
149
  with torch.no_grad():
150
- outputs = model(**inputs, multimask_output=use_multimask)
151
 
152
- masks_result = safe_post_process_masks(processor, outputs, inputs)
153
- if masks_result is None:
154
- print("⚠️ No masks returned by post_process_masks; using basic threshold fallback.")
155
- # fallback: try to create single mask from pred_masks
156
- try:
157
- pred = outputs.pred_masks.cpu().numpy()
158
- if pred.ndim == 4:
159
- mask = pred[0, 0] > 0.5
160
- elif pred.ndim == 3:
161
- mask = pred[0] > 0.5
162
- else:
163
- mask = pred > 0.5
164
- masks = [mask]
165
- except Exception as e:
166
- print("Fallback also failed:", e)
167
- return image, {"error": "Mask processing failed"}
168
- else:
169
- # masks_result is like [[ [mask1_arr, mask2_arr, ... ] ]]
170
- # take first batch
171
- batch_masks = masks_result[0]
172
- # Take flattened mask arrays
173
- if isinstance(batch_masks, list) and len(batch_masks) > 0 and isinstance(batch_masks[0], list):
174
- masks = batch_masks[0]
175
- else:
176
- # Some shapes: try to flatten
177
- masks = []
178
- for item in batch_masks:
179
- if isinstance(item, np.ndarray):
180
- masks.append(item)
181
- # Scores handling (best effort)
182
- try:
183
- scores = outputs.iou_scores.cpu().numpy().flatten()
184
- except Exception:
185
- scores = np.array([0.0] * len(masks))
186
 
187
- # Choose mask: prefer middle mask if multiple and not high GPU
188
- if len(scores) >= 3:
189
- sorted_indices = np.argsort(scores)
190
- best_idx = sorted_indices[1] if len(sorted_indices) > 1 else sorted_indices[-1]
191
- elif len(scores) >= 1:
192
- best_idx = int(np.argmax(scores))
193
- else:
194
- best_idx = 0
195
 
196
- selected_mask = masks[best_idx] if isinstance(masks, list) and len(masks) > best_idx else masks[0]
 
 
 
197
 
198
- # If mask is float 0..1, convert to boolean
199
- selected_mask = (np.array(selected_mask) > 0.5)
 
 
200
 
201
- # Filter keep largest component
202
- final_mask = selected_mask
203
- if filter_largest:
204
- num_labels, labels = cv2.connectedComponents(final_mask.astype(np.uint8))
205
- if num_labels > 1:
206
- largest_component = 0
207
- largest_size = 0
208
- for i in range(1, num_labels):
209
- size = int(np.sum(labels == i))
210
- if size > largest_size:
211
- largest_size = size
212
- largest_component = i
213
- final_mask = (labels == largest_component)
214
 
215
- if quality == "high":
216
- final_mask = refine_mask(final_mask, kernel_size=7)
 
 
 
 
 
217
 
218
- # create overlay
219
  overlay = image_np.copy()
220
- color = np.array([255, 80, 180], dtype=np.uint8)
221
- mask_float = final_mask.astype(float)
 
 
222
  if quality == "high":
223
- mask_float = cv2.GaussianBlur(mask_float, (5, 5), 0)
224
 
 
225
  for c in range(3):
226
- overlay[:, :, c] = (
227
- overlay[:, :, c] * (1 - mask_float * 0.65) +
228
- color[c] * mask_float * 0.65
229
- ).astype(np.uint8)
230
 
231
- # contours
232
- contours, _ = cv2.findContours(final_mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
233
- cv2.drawContours(overlay, contours, -1, (255, 255, 0), 3)
234
 
235
- metadata = {
236
  "success": True,
237
  "mode": "automatic_smart",
238
- "quality": quality,
239
- "filtered": filter_largest,
240
- "selected_mask_score": float(scores[best_idx]) if len(scores) > 0 else 0.0,
241
- "all_scores": scores.tolist() if hasattr(scores, "tolist") else [],
242
  "image_size": [w, h],
243
- "mask_area": int(np.sum(final_mask)),
244
- "mask_percentage": float(np.sum(final_mask) / (h * w) * 100),
245
  "num_contours": len(contours),
246
- "device": device,
247
- "time_s": round(time.time() - start, 2)
248
- }
249
-
250
- return Image.fromarray(overlay.astype(np.uint8)), metadata
251
-
252
- except Exception as e:
253
- print("❌ ERROR in segment_automatic:", e)
254
- traceback.print_exc()
255
- return image, {"error": str(e)}
256
-
257
- # ---- Multi-region CPU-optimized (keine Änderung notwendig, nur small improvements) ----
258
- def segment_multi_dense(image, density="low"):
259
- if image is None:
260
- return None, {"error": "Kein Bild"}
261
-
262
- start = time.time()
263
- try:
264
- model, processor = load_model()
265
- image_pil, image_np = prepare_image(image)
266
- h, w = image_np.shape[:2]
267
-
268
- if density == "high":
269
- grid_size = 4
270
- elif density == "medium":
271
- grid_size = 3
272
- else:
273
- grid_size = 2
274
-
275
- points = []
276
- for i in range(1, grid_size + 1):
277
- for j in range(1, grid_size + 1):
278
- x = int(w * i / (grid_size + 1))
279
- y = int(h * j / (grid_size + 1))
280
- points.append([x, y])
281
-
282
- all_masks = []
283
- all_scores = []
284
-
285
- # use multimask only on GPU to avoid memory issues
286
- use_multimask = True if device == "cuda" else False
287
-
288
- for idx, point in enumerate(points):
289
- inputs = processor(
290
- image_pil,
291
- input_points=[[point]],
292
- input_labels=[[1]],
293
- return_tensors="pt"
294
- ).to(device)
295
-
296
- with torch.no_grad():
297
- outputs = model(**inputs, multimask_output=use_multimask)
298
-
299
- masks_result = safe_post_process_masks(processor, outputs, inputs)
300
- if masks_result is None:
301
- continue
302
-
303
- # flatten masks_result similar to above
304
- try:
305
- batch_masks = masks_result[0]
306
- if isinstance(batch_masks[0], list):
307
- found_masks = batch_masks[0]
308
- else:
309
- # if a list of arrays
310
- found_masks = [m for m in batch_masks]
311
- except Exception:
312
- found_masks = []
313
-
314
- # try to get scores
315
- try:
316
- scores = outputs.iou_scores.cpu().numpy().flatten()
317
- except Exception:
318
- scores = np.array([0.0] * len(found_masks))
319
-
320
- for m_idx, mask_arr in enumerate(found_masks):
321
- mask_bool = (np.array(mask_arr) > 0.5)
322
- best_score = float(scores[m_idx]) if m_idx < len(scores) else 0.0
323
- if best_score > 0.5:
324
- all_masks.append(mask_bool)
325
- all_scores.append(best_score)
326
-
327
- print(f"✅ Got {len(all_masks)} quality masks")
328
-
329
- overlay = image_np.copy()
330
- colors = []
331
- for i in range(len(all_masks)):
332
- hue = int(180 * i / max(len(all_masks), 1))
333
- color_hsv = np.uint8([[[hue, 255, 200]]])
334
- color_rgb = cv2.cvtColor(color_hsv, cv2.COLOR_HSV2RGB)[0][0]
335
- colors.append(color_rgb)
336
-
337
- for mask, color, score in zip(all_masks, colors, all_scores):
338
- alpha = 0.5
339
- overlay[mask] = (
340
- overlay[mask] * (1 - alpha) +
341
- np.array(color) * alpha
342
- ).astype(np.uint8)
343
-
344
- contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
345
- cv2.drawContours(overlay, contours, -1, color.tolist(), 2)
346
-
347
- metadata = {
348
- "success": True,
349
- "mode": "multi_object_fast",
350
- "density": density,
351
- "grid_size": f"{grid_size}x{grid_size}",
352
- "total_points": len(points),
353
- "quality_masks": len(all_masks),
354
- "avg_score": float(np.mean(all_scores)) if all_scores else 0,
355
- "scores": [float(s) for s in all_scores],
356
- "time_s": round(time.time() - start, 2)
357
  }
358
-
359
- return Image.fromarray(overlay), metadata
360
 
361
  except Exception as e:
362
- print("❌ ERROR in segment_multi_dense:", e)
363
- traceback.print_exc()
364
  return image, {"error": str(e)}
365
-
366
- # ---- Gradio UI ----
367
- demo = gr.Blocks(title="SAM2 Boostly (Stable)", theme=gr.themes.Soft())
368
-
369
- with demo:
370
- gr.Markdown("# 🎨 SAM2 Segmentierung - Boostly (Stable, CPU-friendly)")
371
- gr.Markdown("### ⚡ Smart Filtering & CPU Optimized")
372
-
373
- with gr.Tab("🤖 Automatisch SMART"):
374
- gr.Markdown("**Intelligente Filterung** - Nur das Hauptobjekt, keine Jacke/Hintergrund!")
375
- with gr.Row():
376
- with gr.Column():
377
- input_auto = gr.Image(type="pil", label="📸 Bild hochladen")
378
- quality_radio = gr.Radio(
379
- choices=["high", "fast"],
380
- value="fast", # default to fast for CPU
381
- label="⚙️ Qualität",
382
- info="High = glattere Kanten (langsamer)"
383
- )
384
- filter_checkbox = gr.Checkbox(
385
- value=True,
386
- label="🔍 Nur größtes Objekt",
387
- info="Filtert Jacke, Hintergrund, kleine Teile"
388
- )
389
- btn_auto = gr.Button("🚀 Segmentieren", variant="primary", size="lg")
390
-
391
- gr.Markdown("""
392
- **✨ Smart Features:**
393
- - Mittlere Maske (vermeidet Over-Segmentation)
394
- - Connected Components Filtering
395
- - Entfernt automatisch Jacke/Hintergrund
396
- - ⚡ Schnell: empfohlen für CPU = fast / low
397
- """)
398
-
399
- with gr.Column():
400
- output_auto = gr.Image(label="✨ Segmentiertes Bild")
401
- json_auto = gr.JSON(label="📊 Metadata")
402
-
403
- btn_auto.click(
404
- fn=segment_automatic,
405
- inputs=[input_auto, quality_radio, filter_checkbox],
406
- outputs=[output_auto, json_auto]
407
- )
408
-
409
- with gr.Tab("🎯 Multi-Region (Schnell)"):
410
- gr.Markdown("**CPU-Optimiert** - Reduzierte Punkte für schnellere Verarbeitung")
411
- with gr.Row():
412
- with gr.Column():
413
- input_multi = gr.Image(type="pil", label="📸 Bild hochladen")
414
- density_radio = gr.Radio(
415
- choices=["high", "medium", "low"],
416
- value="low",
417
- label="📊 Punkt-Dichte",
418
- info="Start with LOW for tests!"
419
- )
420
- btn_multi = gr.Button("🎯 Segmentieren", variant="primary", size="lg")
421
-
422
- gr.Markdown("""
423
- **⚡ Optimierte Grid-Größen:**
424
- - 💨 Low: 2x2 = 4 Punkte (schnell) ← Empfohlen für CPU
425
- - ⚡ Medium: 3x3 = 9 Punkte
426
- - 🔥 High: 4x4 = 16 Punkte (langsamer)
427
- """)
428
-
429
- with gr.Column():
430
- output_multi = gr.Image(label="✨ Segmentiertes Bild")
431
- json_multi = gr.JSON(label="📊 Metadata")
432
-
433
- btn_multi.click(
434
- fn=segment_multi_dense,
435
- inputs=[input_multi, density_radio],
436
- outputs=[output_multi, json_multi]
437
- )
438
-
439
- with gr.Tab("📡 API Documentation"):
440
- gr.Markdown("### 🔗 API Endpoint")
441
- gr.Code("https://EnginDev-Boostly.hf.space/api/predict", label="Base URL")
442
- gr.Markdown("### ⚙️ Notes")
443
- gr.Markdown("- Default model: facebook/sam-vit-base (CPU friendly)\n- Use low density for tests\n- If you have GPU, change device to cuda and adjust density")
444
-
445
- if __name__ == "__main__":
446
- print("🌐 Launching Boostly SAM2 (Stable)...")
447
- demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
 
 
1
  import numpy as np
 
2
  import cv2
3
+ from PIL import Image
 
 
 
 
 
 
 
4
 
5
+ def safe_to_cpu_np(tensor):
6
+ """Return numpy array or None if tensor is None"""
7
+ if tensor is None:
8
+ return None
9
+ return tensor.detach().cpu().numpy()
10
 
11
+ def postprocess_masks_safe(processor, pred_masks, original_sizes, reshaped_input_sizes):
 
12
  """
13
+ Wrapper: versucht verschiedene Formen / Signaturen zu behandeln.
14
+ Gibt eine Liste von mask arrays (H,W) für das erste Bild zurück.
15
  """
16
+ # ensure numpy
17
+ pred_masks_np = np.asarray(pred_masks)
18
+ # call processor post_process in a robust way
19
  try:
20
+ masks = processor.image_processor.post_process_masks(
21
+ pred_masks_np, np.asarray(original_sizes), np.asarray(reshaped_input_sizes)
 
 
 
22
  )
23
+ except Exception as e:
24
+ # Try alternative signature (some transformers versions accept different args)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  try:
26
+ masks = processor.post_process_masks(pred_masks_np, np.asarray(original_sizes), np.asarray(reshaped_input_sizes))
27
+ except Exception as e2:
28
+ print("postprocess_masks failed:", e, e2)
29
+ return []
30
+ # masks is a list per image - return first (for batch size 1)
31
+ if isinstance(masks, (list, tuple)) and len(masks) > 0:
32
+ return masks[0]
33
+ return []
34
+
35
+ def ensure_mask_bool(mask):
36
+ """normalizes mask to boolean 2D ndarray"""
37
+ if mask is None:
38
+ return None
39
+ a = np.asarray(mask)
40
+ # if mask has channel dim, reduce
41
+ if a.ndim == 3 and a.shape[0] in (1,):
42
+ a = a[0]
43
+ if a.dtype != bool:
44
+ return (a > 0).astype(bool)
45
+ return a
46
+
47
+ def pick_best_mask_by_score(masks_list, scores):
48
+ """Return index of chosen mask; handles missing/short scores"""
49
+ if len(masks_list) == 0:
50
+ return None
51
+ if scores is None or len(scores) == 0:
52
+ return 0
53
+ scores = np.asarray(scores).flatten()
54
+ if len(scores) == 1:
55
+ return 0
56
+ # prefer middle mask as before if at least 3
57
+ if len(scores) >= 3:
58
+ sorted_idx = np.argsort(scores)
59
+ return sorted_idx[1] # middle
60
+ return int(np.argmax(scores))
61
+
62
+ def keep_largest_component_bool(mask_bool):
63
+ """Return boolean mask with only largest connected component"""
64
+ if mask_bool is None:
65
+ return None
66
+ mask_uint8 = (mask_bool.astype(np.uint8) * 255)
67
+ num_labels, labels = cv2.connectedComponents(mask_uint8)
68
+ if num_labels <= 1:
69
+ return mask_bool
70
+ largest = 1
71
+ largest_size = 0
72
+ for i in range(1, num_labels):
73
+ s = int((labels == i).sum())
74
+ if s > largest_size:
75
+ largest_size = s
76
+ largest = i
77
+ return (labels == largest)
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def segment_automatic(image, quality="high", filter_largest=True):
80
  if image is None:
81
  return None, {"error": "Kein Bild hochgeladen"}
 
 
82
  try:
83
  model, processor = load_model()
84
  image_pil, image_np = prepare_image(image)
85
  h, w = image_np.shape[:2]
86
+ cx, cy = w//2, h//2
 
 
 
87
 
88
+ # Build inputs
89
  inputs = processor(
90
  image_pil,
91
+ input_points=[[[cx, cy]]],
92
  input_labels=[[1]],
93
  return_tensors="pt"
94
+ )
95
+ inputs = {k: v.to(device) if hasattr(v, "to") else v for k, v in inputs.items()}
96
 
97
  with torch.no_grad():
98
+ outputs = model(**inputs, multimask_output=True)
99
 
100
+ # safe extraction
101
+ pred_masks = safe_to_cpu_np(getattr(outputs, "pred_masks", None))
102
+ iou_scores = safe_to_cpu_np(getattr(outputs, "iou_scores", None))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ # Get original/reshaped sizes from inputs (move to cpu/numpy if tensors)
105
+ orig_sizes = safe_to_cpu_np(inputs.get("original_sizes", None))
106
+ reshaped_sizes = safe_to_cpu_np(inputs.get("reshaped_input_sizes", None))
 
 
 
 
 
107
 
108
+ # postprocess masks robustly
109
+ masks_post = postprocess_masks_safe(processor, pred_masks, orig_sizes if orig_sizes is not None else [[h, w]], reshaped_sizes if reshaped_sizes is not None else [[h, w]])
110
+ if not masks_post:
111
+ return image, {"error": "keine masken erzeugt (postprocess returned empty)"}
112
 
113
+ # masks_post is list/array: choose best
114
+ idx = pick_best_mask_by_score(masks_post, iou_scores)
115
+ if idx is None:
116
+ return image, {"error": "keine passende maske gefunden"}
117
 
118
+ # normalize mask
119
+ # masks_post might be array of masks or dict -> handle both
120
+ mask_candidate = masks_post[idx] if isinstance(masks_post, (list, tuple, np.ndarray)) else masks_post
121
+ mask_bool = ensure_mask_bool(mask_candidate)
 
 
 
 
 
 
 
 
 
122
 
123
+ # filter largest component
124
+ if filter_largest and mask_bool is not None:
125
+ mask_bool = keep_largest_component_bool(mask_bool)
126
+
127
+ # refine edges if requested
128
+ if quality == "high" and mask_bool is not None:
129
+ mask_bool = refine_mask(mask_bool, kernel_size=7)
130
 
131
+ # overlay coloring
132
  overlay = image_np.copy()
133
+ if mask_bool is None:
134
+ return image, {"error": "maske ist None nach Verarbeitung"}
135
+
136
+ mask_float = mask_bool.astype(float)
137
  if quality == "high":
138
+ mask_float = cv2.GaussianBlur(mask_float, (5,5), 0)
139
 
140
+ color = np.array([255, 80, 180], dtype=np.uint8)
141
  for c in range(3):
142
+ overlay[:,:,c] = (overlay[:,:,c] * (1 - mask_float * 0.65) + color[c] * mask_float * 0.65).astype(np.uint8)
 
 
 
143
 
144
+ contours, _ = cv2.findContours((mask_bool.astype(np.uint8)), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
145
+ cv2.drawContours(overlay, contours, -1, (255,255,0), 3)
 
146
 
147
+ meta = {
148
  "success": True,
149
  "mode": "automatic_smart",
 
 
 
 
150
  "image_size": [w, h],
151
+ "mask_area": int(mask_bool.sum()),
152
+ "mask_percentage": float(mask_bool.sum() / (w*h) * 100),
153
  "num_contours": len(contours),
154
+ "device": device
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
+ return Image.fromarray(overlay), meta
 
157
 
158
  except Exception as e:
159
+ import traceback
160
+ print(traceback.format_exc())
161
  return image, {"error": str(e)}