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

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +71 -46
streamlit_app.py CHANGED
@@ -1,9 +1,9 @@
1
- # streamlit_app.py — Motion Path Planner (Frontend, Public HF Space)
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
9
 
@@ -34,6 +34,7 @@ _defaults = {
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)
@@ -80,13 +81,11 @@ def _center_crop_aspect(img: Image.Image, target_ratio: float) -> Image.Image:
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:
@@ -94,8 +93,7 @@ def _b64_from_pil(img: Image.Image, jpeg_quality=90) -> str:
94
  img.save(buf, format="JPEG", quality=int(jpeg_quality))
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
 
@@ -170,7 +168,7 @@ def _detect_boxes_backend(image_b64: str, labels: List[str]) -> Optional[dict]:
170
  # ================= Sidebar (backend + constraints) =================
171
  with st.sidebar:
172
  st.title("Backend")
173
- st.caption("Point to your private backend Space.")
174
  url_in = st.text_input("Backend URL", value=st.session_state["API_BASE"],
175
  placeholder="https://<org>-backend.hf.space")
176
  colb1, colb2 = st.columns([1,1])
@@ -179,16 +177,19 @@ with st.sidebar:
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)")
191
- st.caption("Upload Flair export to enforce hardware constraints.")
192
  export_file = st.file_uploader("Upload export.txt", type=["txt"], key="exp_txt")
193
  cola, colb = st.columns(2)
194
  with cola:
@@ -199,11 +200,7 @@ with st.sidebar:
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:
@@ -215,16 +212,16 @@ with st.sidebar:
215
  _err(f"{r.status_code}: {r.text[:300]}")
216
 
217
  # ================= Main =================
218
- st.title("Motion Path Planner for Flair")
219
- st.caption("Describe the camera move in English. Get a rig-checked `.xyz` path.")
220
 
221
  with st.expander("How it works"):
222
  st.markdown(
223
  """
224
- 1) Upload a camera image.
225
- 2) Describe the move (e.g., “Orbit 90° in 4s, jib up 0.6 m, radius 0.5 m”).
226
- 3) Generate a `.xyz` compatible with Flair.
227
- Optionally load `export.txt` in the sidebar to enforce rig limits.
228
  """
229
  )
230
 
@@ -254,16 +251,16 @@ with col_img:
254
  img_file = st.file_uploader("Camera image (JPG/PNG)", type=["jpg","jpeg","png"], key="scene")
255
  with col_text:
256
  presets = [
257
- "Orbit 90° in 4 seconds, jib up 0.6 m, radius 0.5 m, keep subject centered.",
258
- "Quick push-in 0.2 m over 3 seconds, then 40° orbit right, keep subject in frame.",
259
- "Fast start, soft finish, orbit -60° in 3.5 seconds, jib down 0.2 m."
260
  ]
261
  preset_pick = st.selectbox("Presets (optional)", options=["(none)"] + presets, index=0)
262
  instr = st.text_area(
263
  "Describe the move",
264
  value=(preset_pick if preset_pick != "(none)" else ""),
265
  height=110,
266
- placeholder="Example: Orbit 90° in 4s, jib up 0.6 m, radius 0.45 m, keep subject centered."
267
  )
268
 
269
  st.markdown("**Subject labels**")
@@ -284,6 +281,7 @@ with col_text:
284
  ["Highest score", "Largest area", "Center-most"],
285
  index=["Highest score", "Largest area", "Center-most"].index(st.session_state["box_pick"])
286
  )
 
287
 
288
  def _gather_labels() -> List[str]:
289
  labels = list(st.session_state.get("primary_labels") or [])
@@ -302,7 +300,6 @@ if img_file:
302
  with det_card:
303
  st.subheader("Preview")
304
  st.caption("Subject placement and detection overlay.")
305
-
306
  boxes_img = None
307
  try:
308
  labels = _gather_labels() or ["subject"]
@@ -317,8 +314,7 @@ if img_file:
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)
324
  else:
@@ -346,7 +342,12 @@ def _payload_prebuilt():
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
352
  if btn_generate and _require_inputs():
@@ -368,9 +369,6 @@ if btn_generate and _require_inputs():
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:
@@ -383,6 +381,8 @@ if btn_report and _require_inputs():
383
  if r.status_code == 200:
384
  data = r.json()
385
  _success("Report ready.")
 
 
386
  m1, m2, m3 = st.columns(3)
387
  with m1:
388
  st.metric("Orbit requested (deg)", f"{data.get('orbit_requested', 0):.1f}")
@@ -394,14 +394,38 @@ if btn_report and _require_inputs():
394
  st.metric("Duration (s)", f"{data.get('duration_s', 0):.2f}")
395
  st.metric("FPS", f"{data.get('fps', 0)}")
396
 
 
 
 
 
 
 
 
 
 
 
397
  ok = bool(data.get("constraints_passed", False))
398
  st.markdown(f"**Constraints:** {'✅ Passed' if ok else '❌ Failed'}")
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]}")
@@ -409,7 +433,7 @@ if btn_report and _require_inputs():
409
  # ---------- Analyze existing .xyz ----------
410
  st.markdown("---")
411
  st.subheader("Analyze a .xyz file")
412
- st.caption("Upload a Flair .xyz to inspect duration, FPS, orbit and jib.")
413
  xyz_up = st.file_uploader("Upload .xyz", type=["xyz"], key="xyz_file")
414
  if xyz_up and st.button("Analyze"):
415
  with st.spinner("Analyzing…"):
@@ -425,5 +449,6 @@ st.markdown("---")
425
  st.caption(
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
- )
 
 
1
+ # streamlit_app.py — AI Motion Path Planner (Frontend)
2
+ # Konuştuğu backend FastAPI uçları:
3
+ # /health, /constraints/upload, /constraints/reset, /detect, /plan, /plan_report, /analyze_xyz
4
 
5
  import os, io, uuid, base64, requests
6
+ from typing import List, Tuple, Optional, Dict, Any
7
  from PIL import Image, ImageDraw, ImageFont, ImageOps
8
  import streamlit as st
9
 
 
34
  "primary_labels": ["plate", "food"],
35
  "extra_labels": "",
36
  "box_pick": "Highest score", # "Highest score", "Largest area", "Center-most"
37
+ "want_notes": True,
38
  }
39
  for k, v in _defaults.items():
40
  st.session_state.setdefault(k, v)
 
81
  y0 = (H - new_h) // 2
82
  return img.crop((0, y0, W, y0 + new_h))
83
 
84
+ def _preprocess_image(file, target_w=1280, target_h=720, 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
+ img = img.resize((target_w, target_h), Image.Resampling.LANCZOS) # esnetme yok: önce crop, sonra resize
 
89
  return img
90
 
91
  def _b64_from_pil(img: Image.Image, jpeg_quality=90) -> str:
 
93
  img.save(buf, format="JPEG", quality=int(jpeg_quality))
94
  return base64.b64encode(buf.getvalue()).decode("utf-8")
95
 
96
+ def _b64_from_file(file, target_w=1280, target_h=720, jpeg_quality=90, aspect_mode="16:9 crop") -> Tuple[str, Image.Image]:
 
97
  img = _preprocess_image(file, target_w, target_h, aspect_mode)
98
  return _b64_from_pil(img, jpeg_quality), img
99
 
 
168
  # ================= Sidebar (backend + constraints) =================
169
  with st.sidebar:
170
  st.title("Backend")
171
+ st.caption("Private backend URL’inizi girin.")
172
  url_in = st.text_input("Backend URL", value=st.session_state["API_BASE"],
173
  placeholder="https://<org>-backend.hf.space")
174
  colb1, colb2 = st.columns([1,1])
 
177
  _success(f"Using {st.session_state['API_BASE']}")
178
  st.rerun()
179
  if colb2.button("Health"):
180
+ try:
181
+ r = _get("/health")
182
+ if r.status_code == 200:
183
+ _success("Backend reachable.")
184
+ st.json(r.json(), expanded=False)
185
+ else:
186
+ _err(f"{r.status_code}: {r.text[:300]}")
187
+ except Exception as e:
188
+ _err(str(e))
189
 
190
  st.markdown("---")
191
  st.subheader("Rig Limits (export.txt)")
192
+ st.caption("Flair export.txt yükleyin; hız/ivme/jerk ve açı limitleri uygulanır.")
193
  export_file = st.file_uploader("Upload export.txt", type=["txt"], key="exp_txt")
194
  cola, colb = st.columns(2)
195
  with cola:
 
200
  r = _post_file("/constraints/upload", "file", export_file.read(), export_file.name)
201
  if r.status_code == 200:
202
  _success("Constraints loaded.")
203
+ st.json(r.json(), expanded=False)
 
 
 
 
204
  else:
205
  _err(f"{r.status_code}: {r.text[:300]}")
206
  with colb:
 
212
  _err(f"{r.status_code}: {r.text[:300]}")
213
 
214
  # ================= Main =================
215
+ st.title("AI Motion Path Planner for Flair")
216
+ st.caption("İngilizce komutla kamera hareketini tarif edin; AI planı .xyz olarak üretir, rig limitlerine göre kontrol eder.")
217
 
218
  with st.expander("How it works"):
219
  st.markdown(
220
  """
221
+ 1) Bir kamera görseli yükleyin.
222
+ 2) Hareketi tarif edin (örn: “Orbit 90° in 4s, jib up 0.6 m, radius 0.5 m, 25 fps”).
223
+ 3) **Generate + Report** ile AI yorumlarını, kinematik tepe değerleri ve kısıt kontrollerini görün.
224
+ 4) **Generate Path** ile `.xyz`’yi indirin.
225
  """
226
  )
227
 
 
251
  img_file = st.file_uploader("Camera image (JPG/PNG)", type=["jpg","jpeg","png"], key="scene")
252
  with col_text:
253
  presets = [
254
+ "Orbit 90° in 4 seconds, jib up 0.6 m, radius 0.5 m, keep subject centered, 25 fps.",
255
+ "Quick push-in 0.2 m over 3 seconds, then 40° orbit right, keep subject in frame, 25 fps.",
256
+ "Fast start, soft finish, orbit -60° in 3.5 seconds, jib down 0.2 m, 25 fps."
257
  ]
258
  preset_pick = st.selectbox("Presets (optional)", options=["(none)"] + presets, index=0)
259
  instr = st.text_area(
260
  "Describe the move",
261
  value=(preset_pick if preset_pick != "(none)" else ""),
262
  height=110,
263
+ placeholder="Example: Orbit 90° in 4s, jib up 0.6 m, radius 0.45 m, 25 fps."
264
  )
265
 
266
  st.markdown("**Subject labels**")
 
281
  ["Highest score", "Largest area", "Center-most"],
282
  index=["Highest score", "Largest area", "Center-most"].index(st.session_state["box_pick"])
283
  )
284
+ st.session_state["want_notes"] = st.checkbox("Show AI Notes in report", value=bool(st.session_state["want_notes"]))
285
 
286
  def _gather_labels() -> List[str]:
287
  labels = list(st.session_state.get("primary_labels") or [])
 
300
  with det_card:
301
  st.subheader("Preview")
302
  st.caption("Subject placement and detection overlay.")
 
303
  boxes_img = None
304
  try:
305
  labels = _gather_labels() or ["subject"]
 
314
  except Exception as e:
315
  _warn(f"Detection preview issue: {e}")
316
 
317
+ max_w = 900 # Sayfayı taşırmadan büyük göster
 
318
  if boxes_img is not None:
319
  st.image(boxes_img, caption="Detection overlay", width=max_w)
320
  else:
 
342
  aspect_mode=st.session_state["aspect_mode"],
343
  )
344
  labels = _gather_labels() or ["subject"]
345
+ return {
346
+ "instruction": instr.strip(),
347
+ "image_b64": img_b64,
348
+ "detect_query": ", ".join(labels),
349
+ "want_notes": bool(st.session_state["want_notes"]),
350
+ }
351
 
352
  # Generate-only
353
  if btn_generate and _require_inputs():
 
369
  data = r.json()
370
  st.error("Violated constraints:"); st.write(data.get("violated", []))
371
  st.info("Suggestions:"); st.write(data.get("suggestions", []))
 
 
 
372
  except Exception:
373
  _err(r.text[:600])
374
  else:
 
381
  if r.status_code == 200:
382
  data = r.json()
383
  _success("Report ready.")
384
+
385
+ # Metrics
386
  m1, m2, m3 = st.columns(3)
387
  with m1:
388
  st.metric("Orbit requested (deg)", f"{data.get('orbit_requested', 0):.1f}")
 
394
  st.metric("Duration (s)", f"{data.get('duration_s', 0):.2f}")
395
  st.metric("FPS", f"{data.get('fps', 0)}")
396
 
397
+ # Kinematics
398
+ kin = data.get("kinematics", {}) or {}
399
+ st.subheader("Kinematics (Pan)")
400
+ colk1, colk2, colk3, colk4 = st.columns(4)
401
+ colk1.metric("Max Speed (°/s)", f"{kin.get('pan_max_dps', 0.0):.2f}")
402
+ colk2.metric("Max Acc (°/s²)", f"{kin.get('pan_max_dps2', 0.0):.2f}")
403
+ colk3.metric("Max Jerk (°/s³)", f"{kin.get('pan_max_dps3', 0.0):.2f}")
404
+ colk4.metric("Avg Speed (°/s)", f"{kin.get('pan_avg_dps', 0.0):.2f}")
405
+
406
+ # Constraints result
407
  ok = bool(data.get("constraints_passed", False))
408
  st.markdown(f"**Constraints:** {'✅ Passed' if ok else '❌ Failed'}")
409
  if not ok:
410
  st.error("Violated constraints:"); st.write(data.get("violated", []))
411
  st.info("Suggestions:"); st.write(data.get("suggestions", []))
412
+
413
+ # Detection summary
414
+ with st.expander("Detection summary"):
415
+ st.json(data.get("detection_summary", {}), expanded=False)
416
+
417
+ # AI Notes (UI’da görünür)
418
+ ai_notes = data.get("ai_notes", []) or []
419
+ if ai_notes:
420
+ st.subheader("AI Notes")
421
+ for n in ai_notes:
422
+ st.write(f"• {n}")
423
+ else:
424
+ st.caption("AI Notes unavailable (no key or parsing fallback).")
425
+
426
+ # Param kaynağı
427
+ st.caption(f"Param source: {data.get('param_source', 'unknown')}")
428
+
429
  st.caption("Use “Generate Path (.xyz)” to download the file.")
430
  else:
431
  _err(f"{r.status_code}: {r.text[:600]}")
 
433
  # ---------- Analyze existing .xyz ----------
434
  st.markdown("---")
435
  st.subheader("Analyze a .xyz file")
436
+ st.caption("Flair `.xyz` yükleyin; süre, FPS, orbit, jib ve kinematik pikleri görün.")
437
  xyz_up = st.file_uploader("Upload .xyz", type=["xyz"], key="xyz_file")
438
  if xyz_up and st.button("Analyze"):
439
  with st.spinner("Analyzing…"):
 
449
  st.caption(
450
  "Set env vars in this Space:\n"
451
  "• MotionPath_AI_API = https://<your-private-backend>.hf.space\n"
452
+ "• MotionPath_AI_TOKEN = <same token as backend>\n"
453
+ "This app shows concise **AI Notes** (not chain-of-thought)."
454
+ )