sattwik21 commited on
Commit
629c5ed
Β·
1 Parent(s): 4ee543e

Update model card + sync app to latest

Browse files
Files changed (3) hide show
  1. README.md +95 -28
  2. app.py +137 -61
  3. requirements.txt +2 -2
README.md CHANGED
@@ -1,14 +1,9 @@
1
  ---
2
  title: "AnyTraverse Studio 🚜"
3
  emoji: "🚜"
4
- colorFrom: "slate"
5
- colorTo: "blue"
6
- sdk: "gradio"
7
- sdk_version: "6.22.0"
8
- app_file: "app.py"
9
- pinned: false
10
- python_version: "3.12"
11
- short_description: "Live off-road traversability evaluation dashboard with Human-in-the-Loop (AnyTraverse)"
12
  tags:
13
  - computer-vision
14
  - robotics
@@ -22,25 +17,97 @@ A live Gradio dashboard for evaluating the **AnyTraverse** zero-shot off-road
22
  traversability framework ([paper](https://arxiv.org/abs/2506.16826),
23
  [PyPI](https://pypi.org/project/anytraverse/)).
24
 
25
- ## Workflow
26
- 1. **Upload a video** of an off-road scene.
27
- 2. Set the traversability preferences (Ο„), scene-similarity threshold,
28
- ROI-uncertainty threshold, ROI bounds and the VLM inference **frame skip**.
29
- 3. Press **▢️ Go / Reset**.
30
- 4. Watch per frame:
31
- - raw image + ROI box Β· traversability map Β· uncertainty map Β· ROI crop
32
- - **attention maps for all prompts** (live, not in the exported video)
33
- - live dual-metric **ROI traversability + uncertainty** line plot
34
- - horizontal **0–1 gauge bars** for the two ROI scores
35
- - the traversal state (`ok` / `unknown_scene` / `unknown_object`)
36
- 5. The run **halts** whenever `traversal_state != OK`. Provide an operator
37
- update like `mud: -0.7; gravel: 0.6` (or just `ok`) and press **Resume**.
38
-
39
- Thresholds and ROI bounds can be edited **live** during a run β€” they are
40
- applied directly to the running pipeline.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  ## Notes
43
- - The VLM weights (CLIPSeg + CLIP) download on first run and are cached.
44
- - The composed analysis video is exported as an H.264 `.mp4` (bundled
45
- `imageio-ffmpeg`, no system ffmpeg required).
46
- - GPU is recommended; the space is configured for a **T4** accelerator.
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: "AnyTraverse Studio 🚜"
3
  emoji: "🚜"
4
+ colorFrom: "blue"
5
+ colorTo: "indigo"
6
+ short_description: "Off-road traversability evaluation with HITL"
 
 
 
 
 
7
  tags:
8
  - computer-vision
9
  - robotics
 
17
  traversability framework ([paper](https://arxiv.org/abs/2506.16826),
18
  [PyPI](https://pypi.org/project/anytraverse/)).
19
 
20
+ Try it online:
21
+ [huggingface.co/spaces/sattwik21/anytraverse-studio](https://huggingface.co/spaces/sattwik21/anytraverse-studio)
22
+ (runs on ZeroGPU), or run it locally on your own machine (see below).
23
+
24
+ ## Features
25
+
26
+ - **Live per-frame evaluation** of any uploaded off-road video:
27
+ raw image + ROI box Β· traversability map Β· uncertainty map Β· ROI crop
28
+ - **Attention maps for all prompts** live in the UI (grid of up to 5 columns,
29
+ aspect-ratio preserved) β€” never baked into the exported video
30
+ - **Human-in-the-Loop**: the run halts whenever the traversal state is not
31
+ `ok`; provide a Ο„ update (e.g. `mud: -0.7; gravel: 0.6`, or just `ok`) and
32
+ press **Resume**
33
+ - **Editable live**: thresholds and ROI bounds update the running pipeline
34
+ without restarting
35
+ - Live dual-metric chart + 0–1 gauge bars + telemetry table
36
+ - Composed analysis video exported as browser-playable H.264 (bundled
37
+ `imageio-ffmpeg`, no system ffmpeg required)
38
+ - Hard **frame skip**: pass every k-th frame to the VLM; skipped frames are
39
+ neither shown in the UI nor written to the video
40
+
41
+ ## Run locally
42
+
43
+ ### 1. Prerequisites
44
+
45
+ - **Linux / macOS / Windows** with **Python 3.12+**
46
+ - A **GPU with CUDA** is strongly recommended (CLIPSeg + CLIP inference).
47
+ CPU works but is slow.
48
+ - [uv](https://docs.astral.sh/uv/) (fast, optional) or `pip` + a virtualenv
49
+
50
+ ### 2. Clone
51
+
52
+ ```bash
53
+ git clone https://huggingface.co/sattwik21/anytraverse-studio
54
+ cd anytraverse-studio
55
+ ```
56
+
57
+ ### 3. Install dependencies
58
+
59
+ With `uv` (recommended):
60
+
61
+ ```bash
62
+ uv sync
63
+ ```
64
+
65
+ Or with plain `pip`:
66
+
67
+ ```bash
68
+ python3 -m venv .venv
69
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
70
+ pip install -r requirements.txt
71
+ ```
72
+
73
+ If `Torch` pulls the wrong build, install the CUDA build explicitly, e.g.:
74
+
75
+ ```bash
76
+ pip install torch --index-url https://download.pytorch.org/whl/cu128
77
+ pip install torchvision --index-url https://download.pytorch.org/whl/cu128
78
+ ```
79
+
80
+ ### 4. Run
81
+
82
+ ```bash
83
+ python app.py
84
+ ```
85
+
86
+ The dashboard opens at http://localhost:7860. Since `share=True` is enabled
87
+ outside Hugging Face Spaces, Gradio will also print a temporary public share
88
+ link at startup β€” you can ignore it for local use.
89
+
90
+ ### 5. Usage
91
+
92
+ 1. Upload an off-road video (`.mp4`, `.mov`, `.avi`).
93
+ 2. Leave Ο„ as `{}` for default preferences, or enter e.g.
94
+ `{"road": 1.0, "grass": 0.0, "bush": -0.8}`.
95
+ 3. Set the **Ref Scene Sim. Threshold**, **ROI Uncertainty Threshold** and
96
+ **Frame Skip**.
97
+ 4. Press **▢️ Go / Reset**.
98
+ 5. When the run halts (`unknown_scene` / `unknown_object`), type an operator
99
+ update in the box and press **βœ… Apply & Resume**.
100
 
101
  ## Notes
102
+
103
+ - The first run downloads the VLM weights (CLIPSeg + CLIP, ~1 GB) and caches
104
+ them locally.
105
+ - `data/weights/` and the generated `*.mp4` files are runtime artifacts; the
106
+ dashboard writes `raw_opencv_temp.mp4` and `anytraverse_h264_output.mp4` in
107
+ the working directory.
108
+
109
+ ## Deployment
110
+
111
+ The interactive Space lives at
112
+ [huggingface.co/spaces/sattwik21/anytraverse-studio](https://huggingface.co/spaces/sattwik21/anytraverse-studio).
113
+ This repo is the model card / source for the same dashboard.
app.py CHANGED
@@ -1,3 +1,8 @@
 
 
 
 
 
1
  import json
2
  import os
3
  import shutil
@@ -208,15 +213,8 @@ def _simulate_state(bgr, prefs, uncert_thresh, frame_idx):
208
  }
209
 
210
 
211
- def seed_state(bgr, box):
212
- h, w, _ = bgr.shape
213
- return {
214
- "raw_bgr": bgr, "roi_bbox": box,
215
- "trav": np.full((h, w), 0.5, np.float32),
216
- "uncert": np.zeros((h, w), np.float32),
217
- "attn_maps": [], "roi_trav": 0.5, "roi_uncert": 0.0, "sim": 1.0,
218
- "state": TraversalState.OK,
219
- }
220
 
221
 
222
  # ---------------------------------------------------------------------------
@@ -246,15 +244,28 @@ def build_grid(p):
246
 
247
 
248
  def build_attn_strip(p):
249
- """All prompt attention maps as one labeled strip (not written to the video)."""
 
250
  raw = p["raw_bgr"]
251
  h, w, _ = raw.shape
252
  att = p["attn_maps"]
253
  if not att:
254
  return np.zeros((h, w, 3), dtype=np.uint8)
255
- cell_w = max(int(w // len(att)), 80)
256
- cells = [add_caption(colorize(m, cell_w, h).copy(), name) for name, m in att]
257
- strip = np.hstack(cells)
 
 
 
 
 
 
 
 
 
 
 
 
258
  return cv2.cvtColor(strip, cv2.COLOR_BGR2RGB)
259
 
260
 
@@ -310,6 +321,66 @@ def initial_render(msg):
310
  EMPTY_DF, None)
311
 
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  # ---------------------------------------------------------------------------
314
  # Main streaming worker. Restarted on "Go / Reset" and on "Resume".
315
  # ---------------------------------------------------------------------------
@@ -343,13 +414,6 @@ def run_evaluation(video_file, pref_json, sim_thresh, uncert_thresh,
343
  if ANYTRAVERSE_AVAILABLE:
344
  yield initial_render(
345
  "πŸ”„ Building AnyTraverse pipeline (first run may download models)…")
346
- session.pipeline = build_pipeline_from_paper(
347
- init_traversabilty_preferences=session.preferences,
348
- ref_scene_similarity_threshold=float(sim_thresh),
349
- roi_uncertainty_threshold=float(uncert_thresh),
350
- roi_x_bounds=(float(rx_min), float(rx_max)),
351
- roi_y_bounds=(float(ry_min), float(ry_max)),
352
- )
353
  else:
354
  session.pipeline = None
355
 
@@ -373,10 +437,7 @@ def run_evaluation(video_file, pref_json, sim_thresh, uncert_thresh,
373
  session.fps, (session.vw * 2, session.vh * 2))
374
  session.frame_idx = 0
375
 
376
- box = bounds_box(float(rx_min), float(rx_max),
377
- float(ry_min), float(ry_max), session.vw, session.vh)
378
  skip = session.skip
379
- last_state = None
380
 
381
  try:
382
  while session.cap.isOpened():
@@ -423,26 +484,26 @@ def run_evaluation(video_file, pref_json, sim_thresh, uncert_thresh,
423
  session.uncert_thresh), df_table(), video=None)
424
  return
425
 
426
- # -------- read + process a display frame --------------------------------
427
  t0 = time.time()
428
  ret, frame_bgr = session.cap.read()
429
  if not ret:
430
  break
431
  session.frame_idx += 1
432
- rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
433
-
434
- run_infer = ((session.frame_idx - 1) % skip == 0) or (last_state is None)
435
- if run_infer:
436
- if ANYTRAVERSE_AVAILABLE and session.pipeline is not None:
437
- st = session.pipeline.step(image=PILImage.fromarray(rgb))
438
- p = unpack_state(st, frame_bgr)
439
- else:
440
- p = _simulate_state(frame_bgr, session.preferences,
441
- session.uncert_thresh, session.frame_idx)
442
- last_state = p
443
  else:
444
- p = dict(last_state) if last_state else seed_state(frame_bgr, box)
445
- p["raw_bgr"] = frame_bgr
446
  session.traversal = p["state"]
447
 
448
  fps = round(1.0 / max(time.time() - t0, 1e-3), 1)
@@ -465,8 +526,7 @@ def run_evaluation(video_file, pref_json, sim_thresh, uncert_thresh,
465
  if session.writer is not None:
466
  session.writer.write(cv2.cvtColor(grid, cv2.COLOR_RGB2BGR))
467
 
468
- status = f"Frame {session.frame_idx} Β· state **`{lbl}`**" + (
469
- "" if run_infer else " Β· (inference skipped, reusing last maps)")
470
 
471
  yield render(
472
  grid, status, False, attn, session.frame_idx, skip, lbl,
@@ -520,11 +580,14 @@ def handle_operator_resume(operator_text):
520
  text = (operator_text or "").strip()
521
  if session.pipeline is not None:
522
  if text and text.lower() != "ok":
523
- session.pipeline.human_call(human_input=text)
524
- session.preferences = dict(session.pipeline.traversability_preferences)
525
- msg = f"βœ… Applied operator Ο„ update `{text}` β€” resuming."
 
 
 
526
  else:
527
- session.pipeline.register_scene()
528
  msg = "βœ… Scene registered (no Ο„ change) β€” resuming."
529
  else:
530
  if text and text.lower() != "ok":
@@ -551,7 +614,7 @@ def simulate_hoc():
551
 
552
  def live_pipeline_update(sim, unc, rxmin, rxmax, rymin, rymax):
553
  """Apply threshold / ROI changes to the running pipeline object on the fly."""
554
- pipe = session.pipeline
555
  if pipe is not None:
556
  try:
557
  pipe._threshold.ref_scene_similarity = float(sim)
@@ -573,7 +636,7 @@ def live_pipeline_update(sim, unc, rxmin, rxmax, rymin, rymax):
573
  MONO = [gr.themes.GoogleFont("IBM Plex Mono"), "DejaVu Sans Mono", "monospace"]
574
 
575
  THEME = gr.themes.Base(
576
- primary_hue=gr.themes.colors.slate,
577
  secondary_hue=gr.themes.colors.gray,
578
  neutral_hue=gr.themes.colors.gray,
579
  font=MONO,
@@ -581,28 +644,36 @@ THEME = gr.themes.Base(
581
  radius_size=gr.themes.sizes.radius_sm,
582
  spacing_size=gr.themes.sizes.spacing_sm,
583
  ).set(
584
- body_background_fill="#0e1013",
585
- body_text_color="#d7dce4",
586
- block_background_fill="#141920",
587
- block_border_color="#242b36",
588
- block_title_background_fill="#0e1013",
589
- block_title_text_color="#9fb0c3",
590
- input_background_fill="#0e1116",
591
- input_border_color="#2a3240",
592
  button_primary_background_fill="#1f6feb",
593
  button_primary_background_fill_hover="#2f7bf5",
594
  button_primary_text_color="#ffffff",
595
- button_secondary_background_fill="#1c232d",
596
- button_secondary_text_color="#c7d2de",
597
  )
598
 
599
  CUSTOM_CSS = """
 
 
 
 
 
600
  .prose h1, .prose h2, .prose h3, .prose p, .prose li, .prose code {
601
  font-family: 'IBM Plex Mono', 'DejaVu Sans Mono', monospace;
602
  }
603
  :root { --body-font: 'IBM Plex Mono', 'DejaVu Sans Mono', monospace; }
604
  footer { display: none !important; }
605
  #status-banner { border-left: 4px solid #1f6feb; padding-left: 12px; }
 
 
 
606
  """
607
 
608
  with gr.Blocks(title="AnyTraverse Studio") as demo:
@@ -613,9 +684,9 @@ with gr.Blocks(title="AnyTraverse Studio") as demo:
613
  # -------- main view --------
614
  live_view = gr.Image(label="Raw+ROI (TL) | Traversability (TR) | "
615
  "Uncertainty (BL) | ROI crop (BR)",
616
- height=360)
617
  attn_view = gr.Image(label="Attention maps (all prompts) β€” live only",
618
- height=120)
619
  status_banner = gr.Markdown(
620
  "### Status: ready β€” upload a video and press ▢️ Go.",
621
  elem_id="status-banner")
@@ -624,7 +695,7 @@ with gr.Blocks(title="AnyTraverse Studio") as demo:
624
  live_plot = gr.LinePlot(x="Frame", y=["ROI Trav", "ROI Unc"],
625
  title="Live ROI Metrics (ROI Trav & ROI "
626
  "Uncert vs threshold)",
627
- height=260, )
628
  metric_bars = gr.HTML(value=bars_html(0.0, 0.0, session.uncert_thresh),
629
  label="ROI Score Gauges")
630
 
@@ -632,8 +703,12 @@ with gr.Blocks(title="AnyTraverse Studio") as demo:
632
  # -------- Controls --------
633
  video_in = gr.File(label="πŸ“Ή Upload Off-Road Video (.mp4, .mov, .avi)",
634
  file_count="single")
635
- pref_input = gr.Code(value=json.dumps(session.preferences, indent=2),
636
- language="json", label="Traversability Preferences (Ο„)")
 
 
 
 
637
  with gr.Row():
638
  sim_thresh = gr.Slider(0.05, 1.0, value=0.8, step=0.05,
639
  label="Ref Scene Sim. Threshold")
@@ -704,8 +779,9 @@ with gr.Blocks(title="AnyTraverse Studio") as demo:
704
 
705
 
706
  if __name__ == "__main__":
 
707
  demo.queue().launch(
708
- share=True, theme=THEME, css=CUSTOM_CSS,
709
  allowed_paths=["."],
710
  server_name="0.0.0.0",
711
  )
 
1
+ try:
2
+ import spaces # noqa: E402 must precede any CUDA-initializing import (ZeroGPU)
3
+ except ImportError: # local dev without the spaces runtime
4
+ spaces = None
5
+
6
  import json
7
  import os
8
  import shutil
 
213
  }
214
 
215
 
216
+ def seed_state(bgr, box): # pragma: no cover - kept unused (records placeholder)
217
+ return None
 
 
 
 
 
 
 
218
 
219
 
220
  # ---------------------------------------------------------------------------
 
244
 
245
 
246
  def build_attn_strip(p):
247
+ """Prompt attention maps as a grid: max 5 columns, wrapping to new rows.
248
+ Each cell keeps the map's native aspect ratio (no distortion)."""
249
  raw = p["raw_bgr"]
250
  h, w, _ = raw.shape
251
  att = p["attn_maps"]
252
  if not att:
253
  return np.zeros((h, w, 3), dtype=np.uint8)
254
+ cell_w = max(int(w // min(len(att), 5)), 80)
255
+ first = to_numpy(att[0][1])
256
+ if first.ndim > 2:
257
+ first = first.reshape(first.shape[-2:])
258
+ ah, aw = first.shape[:2]
259
+ cell_h = max(int(round(cell_w * ah / max(aw, 1))), 1)
260
+ blank = np.zeros((cell_h, cell_w, 3), dtype=np.uint8)
261
+ rows = []
262
+ for i in range(0, len(att), 5):
263
+ cells = [add_caption(colorize(m, cell_w, cell_h).copy(), name)
264
+ for name, m in att[i:i + 5]]
265
+ while len(cells) < 5:
266
+ cells.append(blank.copy())
267
+ rows.append(np.hstack(cells))
268
+ strip = rows[0] if len(rows) == 1 else np.vstack(rows)
269
  return cv2.cvtColor(strip, cv2.COLOR_BGR2RGB)
270
 
271
 
 
321
  EMPTY_DF, None)
322
 
323
 
324
+ # ---------------------------------------------------------------------------
325
+ # ZeroGPU worker(s). All CUDA work must live in a @spaces.GPU function; the
326
+ # decorator is a no-op when the `spaces` runtime is absent (local dev).
327
+ # The pipeline as a singleton is cached in `_VLM` so consecutive frames reuse
328
+ # the loaded model inside the GPU context.
329
+ # ---------------------------------------------------------------------------
330
+ _VLM = {"pipe": None}
331
+
332
+
333
+ def _pipeline_ready():
334
+ return _VLM["pipe"] is not None
335
+
336
+
337
+ def _set_pipe():
338
+ session.pipeline = _VLM["pipe"]
339
+
340
+
341
+ def _vlm_make_pipe(prefs, sim_thresh, uncert_thresh, rx, ry):
342
+ return build_pipeline_from_paper(
343
+ init_traversabilty_preferences=prefs,
344
+ ref_scene_similarity_threshold=float(sim_thresh),
345
+ roi_uncertainty_threshold=float(uncert_thresh),
346
+ roi_x_bounds=(float(rx[0]), float(rx[1])),
347
+ roi_y_bounds=(float(ry[0]), float(ry[1])),
348
+ )
349
+
350
+
351
+ def _vlm_step_raw(frame_bgr, prefs, sim_thresh, uncert_thresh, rx, ry):
352
+ if not _pipeline_ready():
353
+ _VLM["pipe"] = _vlm_make_pipe(prefs, sim_thresh, uncert_thresh, rx, ry)
354
+ st = _VLM["pipe"].step(image=PILImage.fromarray(cv2.cvtColor(frame_bgr,
355
+ cv2.COLOR_BGR2RGB)))
356
+ return unpack_state(st, frame_bgr)
357
+
358
+
359
+ def _vlm_human_raw(text):
360
+ if not _pipeline_ready():
361
+ return None
362
+ _VLM["pipe"].human_call(human_input=text)
363
+ return dict(_VLM["pipe"].traversability_preferences)
364
+
365
+
366
+ def _vlm_register_raw():
367
+ if not _pipeline_ready():
368
+ return None
369
+ _VLM["pipe"].register_scene()
370
+ return None
371
+
372
+
373
+ def _gpu_decorate(fn):
374
+ if spaces is not None:
375
+ return spaces.GPU(duration=120)(fn)
376
+ return fn
377
+
378
+
379
+ vlm_step = _gpu_decorate(_vlm_step_raw)
380
+ vlm_human = _gpu_decorate(_vlm_human_raw)
381
+ vlm_register = _gpu_decorate(_vlm_register_raw)
382
+
383
+
384
  # ---------------------------------------------------------------------------
385
  # Main streaming worker. Restarted on "Go / Reset" and on "Resume".
386
  # ---------------------------------------------------------------------------
 
414
  if ANYTRAVERSE_AVAILABLE:
415
  yield initial_render(
416
  "πŸ”„ Building AnyTraverse pipeline (first run may download models)…")
 
 
 
 
 
 
 
417
  else:
418
  session.pipeline = None
419
 
 
437
  session.fps, (session.vw * 2, session.vh * 2))
438
  session.frame_idx = 0
439
 
 
 
440
  skip = session.skip
 
441
 
442
  try:
443
  while session.cap.isOpened():
 
484
  session.uncert_thresh), df_table(), video=None)
485
  return
486
 
487
+ # -------- read the next display frame ------------------------------
488
  t0 = time.time()
489
  ret, frame_bgr = session.cap.read()
490
  if not ret:
491
  break
492
  session.frame_idx += 1
493
+ # Frame skipping: pass every k-th frame to anytraverse; the frames in
494
+ # between are never shown in the UI nor written to the output video.
495
+ if skip > 1 and (session.frame_idx - 1) % skip != 0:
496
+ continue
497
+
498
+ if ANYTRAVERSE_AVAILABLE:
499
+ p = vlm_step(frame_bgr, session.preferences, session.sim_thresh,
500
+ session.uncert_thresh,
501
+ (float(rx_min), float(rx_max)),
502
+ (float(ry_min), float(ry_max)))
503
+ _set_pipe()
504
  else:
505
+ p = _simulate_state(frame_bgr, session.preferences,
506
+ session.uncert_thresh, session.frame_idx)
507
  session.traversal = p["state"]
508
 
509
  fps = round(1.0 / max(time.time() - t0, 1e-3), 1)
 
526
  if session.writer is not None:
527
  session.writer.write(cv2.cvtColor(grid, cv2.COLOR_RGB2BGR))
528
 
529
+ status = f"Frame {session.frame_idx} Β· state **`{lbl}`**"
 
530
 
531
  yield render(
532
  grid, status, False, attn, session.frame_idx, skip, lbl,
 
580
  text = (operator_text or "").strip()
581
  if session.pipeline is not None:
582
  if text and text.lower() != "ok":
583
+ prefs = vlm_human(text)
584
+ if prefs:
585
+ session.preferences = prefs
586
+ msg = f"βœ… Applied operator Ο„ update `{text}` β€” resuming."
587
+ else:
588
+ msg = f"βœ… (pipeline not built yet) resuming with `{text}`."
589
  else:
590
+ vlm_register()
591
  msg = "βœ… Scene registered (no Ο„ change) β€” resuming."
592
  else:
593
  if text and text.lower() != "ok":
 
614
 
615
  def live_pipeline_update(sim, unc, rxmin, rxmax, rymin, rymax):
616
  """Apply threshold / ROI changes to the running pipeline object on the fly."""
617
+ pipe = _VLM["pipe"]
618
  if pipe is not None:
619
  try:
620
  pipe._threshold.ref_scene_similarity = float(sim)
 
636
  MONO = [gr.themes.GoogleFont("IBM Plex Mono"), "DejaVu Sans Mono", "monospace"]
637
 
638
  THEME = gr.themes.Base(
639
+ primary_hue=gr.themes.colors.blue,
640
  secondary_hue=gr.themes.colors.gray,
641
  neutral_hue=gr.themes.colors.gray,
642
  font=MONO,
 
644
  radius_size=gr.themes.sizes.radius_sm,
645
  spacing_size=gr.themes.sizes.spacing_sm,
646
  ).set(
647
+ body_background_fill="#f6f7f9",
648
+ body_text_color="#1f2937",
649
+ block_background_fill="#ffffff",
650
+ block_border_color="#e2e8f0",
651
+ block_title_background_fill="#f1f5f9",
652
+ block_title_text_color="#475569",
653
+ input_background_fill="#ffffff",
654
+ input_border_color="#cbd5e1",
655
  button_primary_background_fill="#1f6feb",
656
  button_primary_background_fill_hover="#2f7bf5",
657
  button_primary_text_color="#ffffff",
658
+ button_secondary_background_fill="#eef2f6",
659
+ button_secondary_text_color="#334155",
660
  )
661
 
662
  CUSTOM_CSS = """
663
+ html, body { color-scheme: light; }
664
+ .gradio-container {
665
+ max-width: 1320px !important;
666
+ padding: 8px 16px 16px !important;
667
+ }
668
  .prose h1, .prose h2, .prose h3, .prose p, .prose li, .prose code {
669
  font-family: 'IBM Plex Mono', 'DejaVu Sans Mono', monospace;
670
  }
671
  :root { --body-font: 'IBM Plex Mono', 'DejaVu Sans Mono', monospace; }
672
  footer { display: none !important; }
673
  #status-banner { border-left: 4px solid #1f6feb; padding-left: 12px; }
674
+ .blocks-wrap .wrap { row-gap: 6px !important; }
675
+ .block { margin-bottom: 6px !important; }
676
+ .back { background: #f6f7f9; }
677
  """
678
 
679
  with gr.Blocks(title="AnyTraverse Studio") as demo:
 
684
  # -------- main view --------
685
  live_view = gr.Image(label="Raw+ROI (TL) | Traversability (TR) | "
686
  "Uncertainty (BL) | ROI crop (BR)",
687
+ height=300)
688
  attn_view = gr.Image(label="Attention maps (all prompts) β€” live only",
689
+ height=160)
690
  status_banner = gr.Markdown(
691
  "### Status: ready β€” upload a video and press ▢️ Go.",
692
  elem_id="status-banner")
 
695
  live_plot = gr.LinePlot(x="Frame", y=["ROI Trav", "ROI Unc"],
696
  title="Live ROI Metrics (ROI Trav & ROI "
697
  "Uncert vs threshold)",
698
+ height=180, )
699
  metric_bars = gr.HTML(value=bars_html(0.0, 0.0, session.uncert_thresh),
700
  label="ROI Score Gauges")
701
 
 
703
  # -------- Controls --------
704
  video_in = gr.File(label="πŸ“Ή Upload Off-Road Video (.mp4, .mov, .avi)",
705
  file_count="single")
706
+ pref_input = gr.Code(
707
+ value="{}",
708
+ language="json",
709
+ lines=3,
710
+ label="Traversability Preferences (Ο„, JSON) β€” leave {} for defaults",
711
+ )
712
  with gr.Row():
713
  sim_thresh = gr.Slider(0.05, 1.0, value=0.8, step=0.05,
714
  label="Ref Scene Sim. Threshold")
 
779
 
780
 
781
  if __name__ == "__main__":
782
+ on_spaces = bool(os.getenv("SPACE_ID") or os.getenv("HF_SPACE"))
783
  demo.queue().launch(
784
+ share=not on_spaces, theme=THEME, css=CUSTOM_CSS,
785
  allowed_paths=["."],
786
  server_name="0.0.0.0",
787
  )
requirements.txt CHANGED
@@ -2,11 +2,11 @@ accelerate>=1.14.0
2
  anytraverse>=1.0.9
3
  einops>=0.8.2
4
  ffmpeg>=1.4
5
- gradio==6.22.0
6
  imageio-ffmpeg>=0.6.0
7
  opencv-python>=5.0.0.93
8
  pandas>=3.0.5
9
  pillow>=12.3.0
10
  torch>=2.13.0
11
  torchvision>=0.28.0
12
- transformers>=5.14.1
 
2
  anytraverse>=1.0.9
3
  einops>=0.8.2
4
  ffmpeg>=1.4
5
+ gradio>=6.22.0
6
  imageio-ffmpeg>=0.6.0
7
  opencv-python>=5.0.0.93
8
  pandas>=3.0.5
9
  pillow>=12.3.0
10
  torch>=2.13.0
11
  torchvision>=0.28.0
12
+ transformers>=5.14.1