eduardo4547 commited on
Commit
39ce9f1
Β·
verified Β·
1 Parent(s): 8113c51

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -66
app.py CHANGED
@@ -10,11 +10,30 @@ import cv2
10
  from PIL import Image
11
  from transformers import pipeline as hf_pipeline
12
 
13
- # CPU-only: use more threads for better throughput
14
- torch.set_num_threads(4)
15
-
 
 
 
 
 
 
 
16
  sam_vit_pipeline = None
17
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  # ── Renderizado ───────────────────────────────────────────────────────────────
20
  def _render_masks(imagen_rgb: Image.Image, masks: list) -> Image.Image:
@@ -28,28 +47,53 @@ def _render_masks(imagen_rgb: Image.Image, masks: list) -> Image.Image:
28
  return Image.fromarray(blended)
29
 
30
 
31
- # ── SegmentaciΓ³n UI ───────────────────────────────────────────────────────────
32
- def segmentar(imagen: Image.Image):
33
  global sam_vit_pipeline
34
- if imagen is None:
35
- return None, "Sube una imagen para comenzar."
36
-
37
  if sam_vit_pipeline is None:
38
- print("Cargando SAM ViT-Huge (CPU)…")
39
  sam_vit_pipeline = hf_pipeline(
40
  "mask-generation",
41
  model="facebook/sam-vit-huge",
42
- device=-1,
43
  )
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  imagen_rgb = imagen.convert("RGB")
46
  resultado = sam_vit_pipeline(
47
  imagen_rgb,
48
- points_per_batch=32,
49
- pred_iou_thresh=0.95,
50
- stability_score_thresh=0.5,
51
- min_mask_region_area=4500,
52
- box_nms_thresh=0.8,
 
53
  )
54
  if isinstance(resultado, list):
55
  resultado = resultado[0]
@@ -58,14 +102,23 @@ def segmentar(imagen: Image.Image):
58
  if not masks:
59
  return imagen_rgb, "No se detectaron zonas."
60
 
61
- return _render_masks(imagen_rgb, masks), f"βœ… {len(masks)} zonas detectadas automΓ‘ticamente"
 
 
 
 
 
 
62
 
63
 
64
  # ── Endpoint para el backend Docker ──────────────────────────────────────────
 
 
65
  def segment_for_backend(image_np: np.ndarray):
66
  """
67
- Llamado por el backend vΓ­a gradio_client (api_name='/segment').
68
- Entrada : numpy uint8 HΓ—WΓ—3.
 
69
  Salida : (overlay_np, combined_json_str)
70
  """
71
  try:
@@ -73,25 +126,18 @@ def segment_for_backend(image_np: np.ndarray):
73
  empty = np.zeros((100, 100, 3), dtype=np.uint8)
74
  return empty, json.dumps({"masks": [], "label_map_b64": ""})
75
 
76
- global sam_vit_pipeline
77
- if sam_vit_pipeline is None:
78
- print("segment_for_backend: cargando SAM ViT-Huge…")
79
- sam_vit_pipeline = hf_pipeline(
80
- "mask-generation",
81
- model="facebook/sam-vit-huge",
82
- device=-1,
83
- )
84
-
85
  pil_image = Image.fromarray(image_np.astype(np.uint8)).convert("RGB")
86
  h, w = image_np.shape[:2]
87
 
88
  resultado = sam_vit_pipeline(
89
  pil_image,
90
- points_per_batch=32,
91
- pred_iou_thresh=0.95,
92
- stability_score_thresh=0.5,
93
- min_mask_region_area=4500,
94
- box_nms_thresh=0.8,
 
95
  )
96
  if isinstance(resultado, list):
97
  resultado = resultado[0]
@@ -99,7 +145,7 @@ def segment_for_backend(image_np: np.ndarray):
99
  all_masks_raw = resultado.get("masks", [])
100
  masks_bool = [np.array(m).astype(bool) for m in all_masks_raw]
101
 
102
- # Label map: cada pΓ­xel contiene el Γ­ndice de la mΓ‘scara (1-based)
103
  label_map = np.zeros((h, w), dtype=np.uint8)
104
  masks_out = []
105
  for i, mask in enumerate(masks_bool[:254], start=1):
@@ -117,21 +163,20 @@ def segment_for_backend(image_np: np.ndarray):
117
  "bbox_xywh": bbox,
118
  })
119
 
120
- # Codificar label map como PNG en base64
121
  pil_label = Image.fromarray(label_map, mode="L")
122
  buf = io.BytesIO()
123
  pil_label.save(buf, format="PNG")
124
  label_map_b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
125
 
126
- # Overlay de visualizaciΓ³n
127
  overlay_pil = _render_masks(pil_image, masks_bool)
128
  overlay_np = np.array(overlay_pil.convert("RGB"))
129
 
130
  combined = {
131
  "masks": masks_out,
132
  "label_map_b64": label_map_b64,
133
- "entorno": "auto",
134
- "motor": "SAM Auto (Exteriores - Sin Prompts)",
 
135
  }
136
  return overlay_np, json.dumps(combined, ensure_ascii=False)
137
 
@@ -142,39 +187,68 @@ def segment_for_backend(image_np: np.ndarray):
142
 
143
 
144
  # ── UI ────────────────────────────────────────────────────────────────────────
145
- def crear_app():
146
- with gr.Blocks(title="SAM Auto β€” CPU") as demo:
147
- gr.Markdown("# πŸ—οΈ SegmentaciΓ³n AutomΓ‘tica β€” SAM ViT-Huge (CPU)")
 
 
 
 
 
 
 
 
 
 
 
 
148
  gr.Markdown(
149
- "SAM detecta todos los elementos de la imagen de forma automΓ‘tica, "
150
- "sin necesidad de seleccionar zonas ni escribir prompts."
151
  )
152
-
153
  with gr.Row():
154
- imagen_entrada = gr.Image(type="pil", label="Foto del Espacio")
155
- imagen_salida = gr.Image(label="Resultado")
156
-
157
- estado = gr.Markdown()
158
- boton = gr.Button("πŸ” Segmentar", variant="primary")
159
-
160
- boton.click(fn=segmentar, inputs=imagen_entrada, outputs=[imagen_salida, estado])
161
- imagen_entrada.upload(fn=segmentar, inputs=imagen_entrada, outputs=[imagen_salida, estado])
162
-
163
- # Endpoint oculto para el backend Docker β€” componentes en nivel raΓ­z del Blocks
164
- _api_in = gr.Image(type="numpy", label="backend_input", visible=False)
165
- _api_over = gr.Image(type="numpy", label="backend_overlay", visible=False)
166
- _api_json = gr.Textbox(label="backend_json", visible=False)
167
- _api_btn = gr.Button(visible=False)
168
- _api_btn.click(
169
- fn=segment_for_backend,
170
- inputs=[_api_in],
171
- outputs=[_api_over, _api_json],
172
- api_name="segment",
173
- )
174
-
175
- return demo
 
 
 
 
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
- demo = crear_app()
179
  if __name__ == "__main__":
180
  demo.launch()
 
10
  from PIL import Image
11
  from transformers import pipeline as hf_pipeline
12
 
13
+ # ── ZeroGPU shim ─────────────────────────────────────────────────────────────
14
+ try:
15
+ import spaces
16
+ except ImportError:
17
+ class _DummySpaces:
18
+ def GPU(self, fn):
19
+ return fn
20
+ spaces = _DummySpaces()
21
+
22
+ DEVICE = 0 if torch.cuda.is_available() else -1
23
  sam_vit_pipeline = None
24
 
25
+ # ── Parametros sincronizados entre UI y backend ───────────────────────────────
26
+ # Estos valores se actualizan cada vez que el usuario corre "Segmentar" en la UI.
27
+ # segment_for_backend los lee para usar exactamente los mismos.
28
+ PARAMS = {
29
+ "pred_iou_thresh": 0.95,
30
+ "stability_score_thresh": 0.5,
31
+ "points_per_batch": 32,
32
+ "min_mask_region_area": 4500,
33
+ "box_nms_thresh": 0.8,
34
+ "crops_n_layers": 1,
35
+ }
36
+
37
 
38
  # ── Renderizado ───────────────────────────────────────────────────────────────
39
  def _render_masks(imagen_rgb: Image.Image, masks: list) -> Image.Image:
 
47
  return Image.fromarray(blended)
48
 
49
 
50
+ def _load_pipeline():
 
51
  global sam_vit_pipeline
 
 
 
52
  if sam_vit_pipeline is None:
53
+ print("Cargando SAM ViT-Huge...")
54
  sam_vit_pipeline = hf_pipeline(
55
  "mask-generation",
56
  model="facebook/sam-vit-huge",
57
+ device=DEVICE,
58
  )
59
 
60
+
61
+ # ── Segmentacion UI ───────────────────────────────────────────────────────────
62
+ @spaces.GPU
63
+ @torch.no_grad()
64
+ def segmentar(
65
+ imagen: Image.Image,
66
+ pred_iou_thresh: float,
67
+ stability_score_thresh: float,
68
+ points_per_batch: int,
69
+ min_mask_region_area: int,
70
+ box_nms_thresh: float,
71
+ crops_n_layers: int,
72
+ ):
73
+ global PARAMS
74
+ if imagen is None:
75
+ return None, "Sube una imagen para comenzar."
76
+
77
+ # Sincronizar PARAMS con los sliders actuales
78
+ PARAMS.update({
79
+ "pred_iou_thresh": float(pred_iou_thresh),
80
+ "stability_score_thresh": float(stability_score_thresh),
81
+ "points_per_batch": int(points_per_batch),
82
+ "min_mask_region_area": int(min_mask_region_area),
83
+ "box_nms_thresh": float(box_nms_thresh),
84
+ "crops_n_layers": int(crops_n_layers),
85
+ })
86
+
87
+ _load_pipeline()
88
  imagen_rgb = imagen.convert("RGB")
89
  resultado = sam_vit_pipeline(
90
  imagen_rgb,
91
+ points_per_batch=PARAMS["points_per_batch"],
92
+ pred_iou_thresh=PARAMS["pred_iou_thresh"],
93
+ stability_score_thresh=PARAMS["stability_score_thresh"],
94
+ min_mask_region_area=PARAMS["min_mask_region_area"],
95
+ box_nms_thresh=PARAMS["box_nms_thresh"],
96
+ crops_n_layers=PARAMS["crops_n_layers"],
97
  )
98
  if isinstance(resultado, list):
99
  resultado = resultado[0]
 
102
  if not masks:
103
  return imagen_rgb, "No se detectaron zonas."
104
 
105
+ info = (
106
+ f"UI: {len(masks)} zonas | "
107
+ f"iou={PARAMS['pred_iou_thresh']} stab={PARAMS['stability_score_thresh']} "
108
+ f"crops={PARAMS['crops_n_layers']} min_area={PARAMS['min_mask_region_area']} "
109
+ f"nms={PARAMS['box_nms_thresh']} batch={PARAMS['points_per_batch']}"
110
+ )
111
+ return _render_masks(imagen_rgb, masks), info
112
 
113
 
114
  # ── Endpoint para el backend Docker ──────────────────────────────────────────
115
+ @spaces.GPU
116
+ @torch.no_grad()
117
  def segment_for_backend(image_np: np.ndarray):
118
  """
119
+ Llamado por el backend via gradio_client (api_name='/segment').
120
+ Usa los mismos PARAMS que la UI β€” sincronizados al ultimo "Segmentar".
121
+ Entrada : numpy uint8 H x W x 3.
122
  Salida : (overlay_np, combined_json_str)
123
  """
124
  try:
 
126
  empty = np.zeros((100, 100, 3), dtype=np.uint8)
127
  return empty, json.dumps({"masks": [], "label_map_b64": ""})
128
 
129
+ _load_pipeline()
 
 
 
 
 
 
 
 
130
  pil_image = Image.fromarray(image_np.astype(np.uint8)).convert("RGB")
131
  h, w = image_np.shape[:2]
132
 
133
  resultado = sam_vit_pipeline(
134
  pil_image,
135
+ points_per_batch=PARAMS["points_per_batch"],
136
+ pred_iou_thresh=PARAMS["pred_iou_thresh"],
137
+ stability_score_thresh=PARAMS["stability_score_thresh"],
138
+ min_mask_region_area=PARAMS["min_mask_region_area"],
139
+ box_nms_thresh=PARAMS["box_nms_thresh"],
140
+ crops_n_layers=PARAMS["crops_n_layers"],
141
  )
142
  if isinstance(resultado, list):
143
  resultado = resultado[0]
 
145
  all_masks_raw = resultado.get("masks", [])
146
  masks_bool = [np.array(m).astype(bool) for m in all_masks_raw]
147
 
148
+ # Label map: cada pixel contiene el indice de la mascara (1-based, max 254)
149
  label_map = np.zeros((h, w), dtype=np.uint8)
150
  masks_out = []
151
  for i, mask in enumerate(masks_bool[:254], start=1):
 
163
  "bbox_xywh": bbox,
164
  })
165
 
 
166
  pil_label = Image.fromarray(label_map, mode="L")
167
  buf = io.BytesIO()
168
  pil_label.save(buf, format="PNG")
169
  label_map_b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
170
 
 
171
  overlay_pil = _render_masks(pil_image, masks_bool)
172
  overlay_np = np.array(overlay_pil.convert("RGB"))
173
 
174
  combined = {
175
  "masks": masks_out,
176
  "label_map_b64": label_map_b64,
177
+ "entorno": "gpu",
178
+ "motor": "SAM Auto (GPU - ZeroGPU)",
179
+ "params_used": dict(PARAMS),
180
  }
181
  return overlay_np, json.dumps(combined, ensure_ascii=False)
182
 
 
187
 
188
 
189
  # ── UI ────────────────────────────────────────────────────────────────────────
190
+ with gr.Blocks(title="SAM Auto - Segmentacion") as demo:
191
+ gr.Markdown("# Segmentacion Automatica - SAM ViT-Huge")
192
+ gr.Markdown(
193
+ "SAM detecta todos los elementos de la imagen de forma automatica, "
194
+ "sin necesidad de seleccionar zonas ni escribir prompts."
195
+ )
196
+
197
+ with gr.Row():
198
+ imagen_entrada = gr.Image(type="pil", label="Foto del Espacio")
199
+ imagen_salida = gr.Image(label="Resultado")
200
+
201
+ estado = gr.Markdown()
202
+ boton = gr.Button("Segmentar", variant="primary")
203
+
204
+ with gr.Accordion("Parametros de segmentacion (sincronizados con el backend)", open=True):
205
  gr.Markdown(
206
+ "> Los parametros que configures aqui se aplican tanto a la UI como al backend Docker. "
207
+ "Haz clic en **Segmentar** para que el backend adopte los nuevos valores."
208
  )
 
209
  with gr.Row():
210
+ sl_pred_iou = gr.Slider(
211
+ minimum=0.0, maximum=1.0, step=0.01, value=PARAMS["pred_iou_thresh"],
212
+ label="pred_iou_thresh (↑ menos mascaras, mas limpias | HF default: 0.88)"
213
+ )
214
+ sl_stability = gr.Slider(
215
+ minimum=0.0, maximum=1.0, step=0.01, value=PARAMS["stability_score_thresh"],
216
+ label="stability_score_thresh (↑ descarta zonas inestables | HF default: 0.95)"
217
+ )
218
+ with gr.Row():
219
+ sl_batch = gr.Slider(
220
+ minimum=16, maximum=128, step=16, value=PARAMS["points_per_batch"],
221
+ label="points_per_batch (no afecta calidad, solo velocidad)"
222
+ )
223
+ sl_min_area = gr.Slider(
224
+ minimum=0, maximum=5000, step=100, value=PARAMS["min_mask_region_area"],
225
+ label="min_mask_region_area px (↑ filtra zonas pequenas)"
226
+ )
227
+ with gr.Row():
228
+ sl_nms = gr.Slider(
229
+ minimum=0.0, maximum=1.0, step=0.05, value=PARAMS["box_nms_thresh"],
230
+ label="box_nms_thresh (↑ permite mas solapamiento entre mascaras)"
231
+ )
232
+ sl_crops = gr.Slider(
233
+ minimum=0, maximum=2, step=1, value=PARAMS["crops_n_layers"],
234
+ label="crops_n_layers (0=imagen completa Β· 1=+4 recortes Β· 2=+16 recortes, lento)"
235
+ )
236
 
237
+ all_inputs = [imagen_entrada, sl_pred_iou, sl_stability, sl_batch, sl_min_area, sl_nms, sl_crops]
238
+ boton.click(fn=segmentar, inputs=all_inputs, outputs=[imagen_salida, estado])
239
+ imagen_entrada.upload(fn=segmentar, inputs=all_inputs, outputs=[imagen_salida, estado])
240
+
241
+ # Endpoint oculto para el backend Docker
242
+ _api_in = gr.Image(type="numpy", label="backend_input", visible=False)
243
+ _api_over = gr.Image(type="numpy", label="backend_overlay", visible=False)
244
+ _api_json = gr.Textbox(label="backend_json", visible=False)
245
+ _api_btn = gr.Button(visible=False)
246
+ _api_btn.click(
247
+ fn=segment_for_backend,
248
+ inputs=[_api_in],
249
+ outputs=[_api_over, _api_json],
250
+ api_name="segment",
251
+ )
252
 
 
253
  if __name__ == "__main__":
254
  demo.launch()