renderfy commited on
Commit
c669e3c
·
verified ·
1 Parent(s): e148097

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +41 -60
streamlit_app.py CHANGED
@@ -2,7 +2,7 @@
2
  # Talks to backend FastAPI: /health, /constraints/upload, /constraints/reset,
3
  # /detect, /plan, /plan_report, /analyze_xyz
4
 
5
- import os, io, uuid, base64, requests, math
6
  from typing import List, Tuple, Optional
7
  from PIL import Image, ImageDraw, ImageFont, ImageOps
8
  import streamlit as st
@@ -30,11 +30,10 @@ _defaults = {
30
  "API_BASE": DEFAULT_API_BASE,
31
  "size_preset": "1280×720",
32
  "jpeg_q": 90,
33
- "keep_aspect": True,
34
  "aspect_mode": "16:9 crop", # "Original", "16:9 crop", "4:3 crop", "1:1 crop"
35
- "primary_labels": ["plate"],
36
- "extra_labels": "food, burger",
37
- "box_pick": "Center-most", # "Highest score", "Largest area", "Center-most"
38
  }
39
  for k, v in _defaults.items():
40
  st.session_state.setdefault(k, v)
@@ -73,26 +72,21 @@ def _center_crop_aspect(img: Image.Image, target_ratio: float) -> Image.Image:
73
  if abs(cur_ratio - target_ratio) < 1e-3:
74
  return img
75
  if cur_ratio > target_ratio:
76
- # too wide -> crop left/right
77
  new_w = int(H * target_ratio)
78
  x0 = (W - new_w) // 2
79
  return img.crop((x0, 0, x0 + new_w, H))
80
  else:
81
- # too tall -> crop top/bottom
82
  new_h = int(W / target_ratio)
83
  y0 = (H - new_h) // 2
84
  return img.crop((0, y0, W, y0 + new_h))
85
 
86
  def _preprocess_image(file, target_w=1280, target_h=720,
87
- keep_aspect=True, aspect_mode="16:9 crop") -> Image.Image:
88
  img = Image.open(file).convert("RGB")
89
  if aspect_mode in _ASPECTS:
90
  img = _center_crop_aspect(img, _ASPECTS[aspect_mode])
91
- # keep_aspect True => distort yok; True iken Image.resize zaten orana göre değil boyuta göre örnekler
92
- # Bu yüzden önce crop ile oranı sabitliyoruz, sonra hedef boyuta resize ediyoruz.
93
- img = img.resize((target_w, target_h), Image.Resampling.LANCZOS) if keep_aspect else ImageOps.fit(
94
- img, (target_w, target_h), method=Image.Resampling.LANCZOS, centering=(0.5, 0.5)
95
- )
96
  return img
97
 
98
  def _b64_from_pil(img: Image.Image, jpeg_quality=90) -> str:
@@ -101,8 +95,8 @@ def _b64_from_pil(img: Image.Image, jpeg_quality=90) -> str:
101
  return base64.b64encode(buf.getvalue()).decode("utf-8")
102
 
103
  def _b64_from_file(file, target_w=1280, target_h=720, jpeg_quality=90,
104
- keep_aspect=True, aspect_mode="16:9 crop") -> Tuple[str, Image.Image]:
105
- img = _preprocess_image(file, target_w, target_h, keep_aspect, aspect_mode)
106
  return _b64_from_pil(img, jpeg_quality), img
107
 
108
  # ================= Overlay drawing =================
@@ -146,20 +140,19 @@ def _draw_boxes(
146
  def _pick_primary_box(
147
  boxes: List[Tuple[float, float, float, float]],
148
  scores: List[float],
149
- strategy: str = "Center-most",
150
  ) -> Optional[int]:
151
  if not boxes:
152
  return None
153
  if strategy == "Largest area":
154
  areas = [(b[2]-b[0]) * (b[3]-b[1]) for b in boxes]
155
  return int(max(range(len(boxes)), key=lambda i: areas[i]))
156
- if strategy == "Highest score":
157
- return int(max(range(len(boxes)), key=lambda i: scores[i] if scores and i < len(scores) else -1e9))
158
- # Center-most (default)
159
- def center_dist(b):
160
- cx = 0.5 * (b[0]+b[2]); cy = 0.5 * (b[1]+b[3])
161
- return (cx-0.5)**2 + (cy-0.5)**2
162
- return int(min(range(len(boxes)), key=lambda i: center_dist(boxes[i])))
163
 
164
  def _detect_boxes_backend(image_b64: str, labels: List[str]) -> Optional[dict]:
165
  payload = {"image_b64": image_b64, "detect_query": ", ".join(labels)}
@@ -186,15 +179,12 @@ with st.sidebar:
186
  _success(f"Using {st.session_state['API_BASE']}")
187
  st.rerun()
188
  if colb2.button("Health"):
189
- try:
190
- r = _get("/health")
191
- if r.status_code == 200:
192
- _success("Backend reachable.")
193
- st.json(r.json(), expanded=False)
194
- else:
195
- _err(f"{r.status_code}: {r.text[:300]}")
196
- except Exception as e:
197
- _err(f"Health error: {e}")
198
 
199
  st.markdown("---")
200
  st.subheader("Rig Limits (export.txt)")
@@ -209,7 +199,11 @@ with st.sidebar:
209
  r = _post_file("/constraints/upload", "file", export_file.read(), export_file.name)
210
  if r.status_code == 200:
211
  _success("Constraints loaded.")
212
- st.json(r.json(), expanded=False)
 
 
 
 
213
  else:
214
  _err(f"{r.status_code}: {r.text[:300]}")
215
  with colb:
@@ -247,7 +241,6 @@ with st.expander("Image optimization"):
247
  ["Original", "16:9 crop", "4:3 crop", "1:1 crop"],
248
  index=["Original", "16:9 crop", "4:3 crop", "1:1 crop"].index(st.session_state["aspect_mode"])
249
  )
250
- st.session_state["keep_aspect"] = True # Do not allow distortion in UI
251
 
252
  def _target_hw():
253
  preset = st.session_state.get("size_preset", "1280×720")
@@ -277,7 +270,7 @@ with col_text:
277
  all_suggestions = ["plate", "food", "burger", "person", "bottle", "product"]
278
  st.session_state["primary_labels"] = st.multiselect(
279
  "Primary labels (chips)",
280
- options=sorted(set(all_suggestions + (st.session_state.get("primary_labels") or []))),
281
  default=st.session_state["primary_labels"],
282
  key="primary_labels_widget",
283
  )
@@ -295,15 +288,7 @@ with col_text:
295
  def _gather_labels() -> List[str]:
296
  labels = list(st.session_state.get("primary_labels") or [])
297
  extra = [x.strip() for x in (st.session_state.get("extra_labels") or "").split(",") if x.strip()]
298
- # Single label often more stable; still allow multi (cap to 12)
299
- merged = [l for l in (labels + extra) if l]
300
- # Deduplicate keeping order
301
- seen = set()
302
- out = []
303
- for l in merged:
304
- if l not in seen:
305
- out.append(l); seen.add(l)
306
- return out[:12] if out else ["subject"]
307
 
308
  # ---------- Detection preview card ----------
309
  det_card = st.container()
@@ -311,7 +296,6 @@ if img_file:
311
  tw, th = _target_hw()
312
  img_proc = _preprocess_image(
313
  img_file, tw, th,
314
- keep_aspect=True,
315
  aspect_mode=st.session_state["aspect_mode"],
316
  )
317
  b64_img = _b64_from_pil(img_proc, int(st.session_state["jpeg_q"]))
@@ -321,22 +305,19 @@ if img_file:
321
 
322
  boxes_img = None
323
  try:
324
- labels = _gather_labels()
325
  data = _detect_boxes_backend(b64_img, labels)
326
  if data and isinstance(data.get("boxes"), list) and len(data["boxes"]) > 0:
327
  boxes = data["boxes"]
328
  lbls = data.get("labels", ["object"] * len(boxes))
329
  scrs = data.get("scores", [0.0] * len(boxes))
330
- # Prefer backend's primary index if provided
331
- primary_idx = data.get("primary_index", None)
332
- if primary_idx is None or not (0 <= int(primary_idx) < len(boxes)):
333
- primary_idx = _pick_primary_box(boxes, scrs, st.session_state["box_pick"])
334
- vis = _draw_boxes(img_proc, boxes, lbls, scrs, primary_idx=int(primary_idx) if primary_idx is not None else None)
335
  boxes_img = vis
336
  except Exception as e:
337
  _warn(f"Detection preview issue: {e}")
338
 
339
- # Show at comfortable width, preserve aspect (no stretch)
340
  max_w = 900
341
  if boxes_img is not None:
342
  st.image(boxes_img, caption="Detection overlay", width=max_w)
@@ -362,10 +343,9 @@ def _payload_prebuilt():
362
  img_b64, _img = _b64_from_file(
363
  img_file, tw, th,
364
  jpeg_quality=int(st.session_state["jpeg_q"]),
365
- keep_aspect=True,
366
  aspect_mode=st.session_state["aspect_mode"],
367
  )
368
- labels = _gather_labels()
369
  return {"instruction": instr.strip(), "image_b64": img_b64, "detect_query": ", ".join(labels)}
370
 
371
  # Generate-only
@@ -388,6 +368,9 @@ if btn_generate and _require_inputs():
388
  data = r.json()
389
  st.error("Violated constraints:"); st.write(data.get("violated", []))
390
  st.info("Suggestions:"); st.write(data.get("suggestions", []))
 
 
 
391
  except Exception:
392
  _err(r.text[:600])
393
  else:
@@ -416,11 +399,9 @@ if btn_report and _require_inputs():
416
  if not ok:
417
  st.error("Violated constraints:"); st.write(data.get("violated", []))
418
  st.info("Suggestions:"); st.write(data.get("suggestions", []))
419
- # diagnostics (if present)
420
- diag = data.get("diag", {})
421
- if diag:
422
- with st.expander("Motion diagnostics"):
423
- st.write(diag)
424
  st.caption("Use “Generate Path (.xyz)” to download the file.")
425
  else:
426
  _err(f"{r.status_code}: {r.text[:600]}")
@@ -445,4 +426,4 @@ st.caption(
445
  "Set env vars in this Space:\n"
446
  "• MotionPath_AI_API = https://<your-private-backend>.hf.space\n"
447
  "• MotionPath_AI_TOKEN = <same token as backend>"
448
- )
 
2
  # Talks to backend FastAPI: /health, /constraints/upload, /constraints/reset,
3
  # /detect, /plan, /plan_report, /analyze_xyz
4
 
5
+ import os, io, uuid, base64, requests
6
  from typing import List, Tuple, Optional
7
  from PIL import Image, ImageDraw, ImageFont, ImageOps
8
  import streamlit as st
 
30
  "API_BASE": DEFAULT_API_BASE,
31
  "size_preset": "1280×720",
32
  "jpeg_q": 90,
 
33
  "aspect_mode": "16:9 crop", # "Original", "16:9 crop", "4:3 crop", "1:1 crop"
34
+ "primary_labels": ["plate", "food"],
35
+ "extra_labels": "",
36
+ "box_pick": "Highest score", # "Highest score", "Largest area", "Center-most"
37
  }
38
  for k, v in _defaults.items():
39
  st.session_state.setdefault(k, v)
 
72
  if abs(cur_ratio - target_ratio) < 1e-3:
73
  return img
74
  if cur_ratio > target_ratio:
 
75
  new_w = int(H * target_ratio)
76
  x0 = (W - new_w) // 2
77
  return img.crop((x0, 0, x0 + new_w, H))
78
  else:
 
79
  new_h = int(W / target_ratio)
80
  y0 = (H - new_h) // 2
81
  return img.crop((0, y0, W, y0 + new_h))
82
 
83
  def _preprocess_image(file, target_w=1280, target_h=720,
84
+ aspect_mode="16:9 crop") -> Image.Image:
85
  img = Image.open(file).convert("RGB")
86
  if aspect_mode in _ASPECTS:
87
  img = _center_crop_aspect(img, _ASPECTS[aspect_mode])
88
+ # Esnetme yok sadece yüksek kaliteli resize
89
+ img = img.resize((target_w, target_h), Image.Resampling.LANCZOS)
 
 
 
90
  return img
91
 
92
  def _b64_from_pil(img: Image.Image, jpeg_quality=90) -> str:
 
95
  return base64.b64encode(buf.getvalue()).decode("utf-8")
96
 
97
  def _b64_from_file(file, target_w=1280, target_h=720, jpeg_quality=90,
98
+ aspect_mode="16:9 crop") -> Tuple[str, Image.Image]:
99
+ img = _preprocess_image(file, target_w, target_h, aspect_mode)
100
  return _b64_from_pil(img, jpeg_quality), img
101
 
102
  # ================= Overlay drawing =================
 
140
  def _pick_primary_box(
141
  boxes: List[Tuple[float, float, float, float]],
142
  scores: List[float],
143
+ strategy: str = "Highest score",
144
  ) -> Optional[int]:
145
  if not boxes:
146
  return None
147
  if strategy == "Largest area":
148
  areas = [(b[2]-b[0]) * (b[3]-b[1]) for b in boxes]
149
  return int(max(range(len(boxes)), key=lambda i: areas[i]))
150
+ if strategy == "Center-most":
151
+ def center_dist(b):
152
+ cx = 0.5 * (b[0]+b[2]); cy = 0.5 * (b[1]+b[3])
153
+ return (cx-0.5)**2 + (cy-0.5)**2
154
+ return int(min(range(len(boxes)), key=lambda i: center_dist(boxes[i])))
155
+ return int(max(range(len(boxes)), key=lambda i: scores[i] if scores and i < len(scores) else -1e9))
 
156
 
157
  def _detect_boxes_backend(image_b64: str, labels: List[str]) -> Optional[dict]:
158
  payload = {"image_b64": image_b64, "detect_query": ", ".join(labels)}
 
179
  _success(f"Using {st.session_state['API_BASE']}")
180
  st.rerun()
181
  if colb2.button("Health"):
182
+ r = _get("/health")
183
+ if r.status_code == 200:
184
+ _success("Backend reachable.")
185
+ st.json(r.json(), expanded=False)
186
+ else:
187
+ _err(f"{r.status_code}: {r.text[:300]}")
 
 
 
188
 
189
  st.markdown("---")
190
  st.subheader("Rig Limits (export.txt)")
 
199
  r = _post_file("/constraints/upload", "file", export_file.read(), export_file.name)
200
  if r.status_code == 200:
201
  _success("Constraints loaded.")
202
+ data = r.json()
203
+ st.json(data.get("parsed", {}), expanded=False)
204
+ if "parser_log" in data:
205
+ with st.expander("Parser log (constraints)"):
206
+ st.json(data["parser_log"], expanded=False)
207
  else:
208
  _err(f"{r.status_code}: {r.text[:300]}")
209
  with colb:
 
241
  ["Original", "16:9 crop", "4:3 crop", "1:1 crop"],
242
  index=["Original", "16:9 crop", "4:3 crop", "1:1 crop"].index(st.session_state["aspect_mode"])
243
  )
 
244
 
245
  def _target_hw():
246
  preset = st.session_state.get("size_preset", "1280×720")
 
270
  all_suggestions = ["plate", "food", "burger", "person", "bottle", "product"]
271
  st.session_state["primary_labels"] = st.multiselect(
272
  "Primary labels (chips)",
273
+ options=sorted(set(all_suggestions + st.session_state.get("primary_labels", []))),
274
  default=st.session_state["primary_labels"],
275
  key="primary_labels_widget",
276
  )
 
288
  def _gather_labels() -> List[str]:
289
  labels = list(st.session_state.get("primary_labels") or [])
290
  extra = [x.strip() for x in (st.session_state.get("extra_labels") or "").split(",") if x.strip()]
291
+ return [l for l in (labels + extra) if l][:12]
 
 
 
 
 
 
 
 
292
 
293
  # ---------- Detection preview card ----------
294
  det_card = st.container()
 
296
  tw, th = _target_hw()
297
  img_proc = _preprocess_image(
298
  img_file, tw, th,
 
299
  aspect_mode=st.session_state["aspect_mode"],
300
  )
301
  b64_img = _b64_from_pil(img_proc, int(st.session_state["jpeg_q"]))
 
305
 
306
  boxes_img = None
307
  try:
308
+ labels = _gather_labels() or ["subject"]
309
  data = _detect_boxes_backend(b64_img, labels)
310
  if data and isinstance(data.get("boxes"), list) and len(data["boxes"]) > 0:
311
  boxes = data["boxes"]
312
  lbls = data.get("labels", ["object"] * len(boxes))
313
  scrs = data.get("scores", [0.0] * len(boxes))
314
+ pick = _pick_primary_box(boxes, scrs, st.session_state["box_pick"])
315
+ vis = _draw_boxes(img_proc, boxes, lbls, scrs, primary_idx=pick)
 
 
 
316
  boxes_img = vis
317
  except Exception as e:
318
  _warn(f"Detection preview issue: {e}")
319
 
320
+ # Esnetmeden, kullanışlı genişlikte göster (aspect otomatik korunur)
321
  max_w = 900
322
  if boxes_img is not None:
323
  st.image(boxes_img, caption="Detection overlay", width=max_w)
 
343
  img_b64, _img = _b64_from_file(
344
  img_file, tw, th,
345
  jpeg_quality=int(st.session_state["jpeg_q"]),
 
346
  aspect_mode=st.session_state["aspect_mode"],
347
  )
348
+ labels = _gather_labels() or ["subject"]
349
  return {"instruction": instr.strip(), "image_b64": img_b64, "detect_query": ", ".join(labels)}
350
 
351
  # Generate-only
 
368
  data = r.json()
369
  st.error("Violated constraints:"); st.write(data.get("violated", []))
370
  st.info("Suggestions:"); st.write(data.get("suggestions", []))
371
+ if "parser_log" in data:
372
+ with st.expander("Parser log (instruction)"):
373
+ st.json(data["parser_log"], expanded=False)
374
  except Exception:
375
  _err(r.text[:600])
376
  else:
 
399
  if not ok:
400
  st.error("Violated constraints:"); st.write(data.get("violated", []))
401
  st.info("Suggestions:"); st.write(data.get("suggestions", []))
402
+ if "parser_log" in data:
403
+ with st.expander("Parser log (instruction)"):
404
+ st.json(data["parser_log"], expanded=False)
 
 
405
  st.caption("Use “Generate Path (.xyz)” to download the file.")
406
  else:
407
  _err(f"{r.status_code}: {r.text[:600]}")
 
426
  "Set env vars in this Space:\n"
427
  "• MotionPath_AI_API = https://<your-private-backend>.hf.space\n"
428
  "• MotionPath_AI_TOKEN = <same token as backend>"
429
+ )