emirkisa commited on
Commit
4dd8acd
·
verified ·
1 Parent(s): 25a87a9

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +95 -31
app.py CHANGED
@@ -380,22 +380,58 @@ def start_precache(fps: int = DEFAULT_FPS, workers: int = 4) -> None:
380
 
381
  # ── Gallery helpers ────────────────────────────────────────────────────────────
382
 
 
 
 
 
383
  def _make_thumb(seq: str, overlay: bool = False, alpha: float = 0.0) -> Image.Image:
384
- fps = _get_frame_paths(seq)
385
- if not fps:
386
- return Image.new("RGB", (THUMB_W, THUMB_H), 30)
387
- img = render_frame(seq, 0, overlay, alpha) if overlay else Image.open(fps[0]).convert("RGB")
388
- img = img.copy()
389
- img.thumbnail((THUMB_W, THUMB_H), Image.LANCZOS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  return img
391
 
392
 
393
- def build_gallery_items(seqs: list[str], overlay: bool = False) -> list[tuple]:
 
 
 
 
 
 
394
  items = []
395
  for seq in seqs:
396
  row = DF[DF["sequence"] == seq].iloc[0]
397
- caption = f"{seq} [{row['frames']}f · {row['n_objects']}obj]"
398
- items.append((_make_thumb(seq, overlay), caption))
 
 
 
 
 
 
 
 
 
399
  return items
400
 
401
 
@@ -718,19 +754,24 @@ def build_ui():
718
  g_f_inputs = [g_year, g_split, g_obj, g_fmin, g_fmax, g_srch]
719
 
720
  def _on_g_filter(*args):
721
- ov = args[-1]
722
- fargs = args[:-1]
723
- fdf = filter_df(*fargs)
724
- seqs = fdf["sequence"].tolist()
725
- items = build_gallery_items(seqs, overlay=ov)
 
 
 
 
 
726
  return (items, seqs,
727
- f"**{len(seqs)} sequences** — click thumbnails to toggle (max 4)")
728
 
729
  for inp in g_f_inputs + [g_ov_th]:
730
- inp.change(_on_g_filter, g_f_inputs + [g_ov_th],
731
  [gallery, g_seq_state, g_count_md])
732
 
733
- # ── Toggle helpers ─────────────────────────────────────────
734
  def _build_video_updates(sel_seqs, ov, a, fps):
735
  """Return 4 gr.update() objects for video slots 0-3."""
736
  updates = []
@@ -747,11 +788,18 @@ def build_ui():
747
  updates.append(gr.update(visible=False, value=None))
748
  return updates
749
 
 
 
 
 
 
 
750
  # ── Gallery click → toggle selection ──────────────────────
751
  def _on_gallery_toggle(evt: gr.SelectData,
752
- sel_seqs, g_seqs, ov, a, fps):
753
  if evt is None or not g_seqs:
754
- return (sel_seqs,
 
755
  gr.update(visible=True), gr.update(visible=False),
756
  gr.update(visible=False, value=None),
757
  gr.update(visible=False, value=None),
@@ -760,32 +808,44 @@ def build_ui():
760
  gr.update(visible=False))
761
 
762
  seq = g_seqs[evt.index]
 
763
  if seq in sel_seqs:
764
  sel_seqs = [s for s in sel_seqs if s != seq]
765
- elif len(sel_seqs) < 4:
766
  sel_seqs = sel_seqs + [seq]
767
- # else: already 4 selectedsilently ignore
768
 
769
  n = len(sel_seqs)
 
770
  vid_updates = _build_video_updates(sel_seqs, ov, a, fps)
771
- info_txt = ("▶ " +
772
- " · ".join(f"**{s}**" for s in sel_seqs) +
773
- " *(click a thumbnail to deselect)*") if n > 0 else ""
 
 
 
 
 
 
 
774
  return (
775
  sel_seqs,
776
- gr.update(visible=(n == 0)), # placeholder
777
- gr.update(visible=(n > 0), value=info_txt), # sel_info
 
778
  vid_updates[0],
779
  vid_updates[1],
780
  vid_updates[2],
781
  vid_updates[3],
782
- gr.update(visible=(n > 0)), # clear button
783
  )
784
 
785
  gallery.select(
786
  _on_gallery_toggle,
787
- inputs=[g_selected_state, g_seq_state, g_vid_ov, g_vid_a, g_fps],
788
- outputs=[g_selected_state, g_placeholder, g_sel_info,
 
 
789
  g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
790
  )
791
 
@@ -801,9 +861,11 @@ def build_ui():
801
  )
802
 
803
  # Clear selection button
804
- def _clear_selection():
 
805
  return (
806
  [],
 
807
  gr.update(visible=True),
808
  gr.update(visible=False, value=""),
809
  gr.update(visible=False, value=None),
@@ -815,7 +877,9 @@ def build_ui():
815
 
816
  g_clr_btn.click(
817
  _clear_selection,
818
- outputs=[g_selected_state, g_placeholder, g_sel_info,
 
 
819
  g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
820
  )
821
 
 
380
 
381
  # ── Gallery helpers ────────────────────────────────────────────────────────────
382
 
383
+ # In-memory thumbnail cache keyed by (seq, overlay, alpha_bucket)
384
+ _thumb_cache: dict[tuple, Image.Image] = {}
385
+
386
+
387
  def _make_thumb(seq: str, overlay: bool = False, alpha: float = 0.0) -> Image.Image:
388
+ """Return a fresh copy of the (cached) base thumbnail."""
389
+ key = (seq, overlay, round(alpha, 2))
390
+ if key not in _thumb_cache:
391
+ fps = _get_frame_paths(seq)
392
+ if not fps:
393
+ img = Image.new("RGB", (THUMB_W, THUMB_H), 30)
394
+ else:
395
+ img = (render_frame(seq, 0, overlay, alpha)
396
+ if overlay else Image.open(fps[0]).convert("RGB"))
397
+ img.thumbnail((THUMB_W, THUMB_H), Image.LANCZOS)
398
+ _thumb_cache[key] = img
399
+ return _thumb_cache[key].copy()
400
+
401
+
402
+ def _apply_selection_glow(img: Image.Image) -> Image.Image:
403
+ """Draw a multi-layer golden glow border indicating a selected thumbnail."""
404
+ from PIL import ImageDraw
405
+ draw = ImageDraw.Draw(img)
406
+ w, h = img.size
407
+ # 5-pixel layered glow: bright gold → orange → dark, outermost first
408
+ glow = [(255, 230, 0), (255, 195, 0), (240, 150, 0), (200, 100, 0), (140, 60, 0)]
409
+ for i, color in enumerate(glow):
410
+ draw.rectangle([i, i, w - 1 - i, h - 1 - i], outline=color)
411
  return img
412
 
413
 
414
+ def build_gallery_items(
415
+ seqs: list[str],
416
+ overlay: bool = False,
417
+ selected: set[str] | None = None,
418
+ max_reached: bool = False,
419
+ ) -> list[tuple]:
420
+ selected = selected or set()
421
  items = []
422
  for seq in seqs:
423
  row = DF[DF["sequence"] == seq].iloc[0]
424
+ img = _make_thumb(seq, overlay)
425
+ if seq in selected:
426
+ img = _apply_selection_glow(img)
427
+ caption = f"✅ {seq} [{row['frames']}f · {row['n_objects']}obj]"
428
+ elif max_reached:
429
+ # Dim thumbnails that can't be added
430
+ img = img.point(lambda p: int(p * 0.45))
431
+ caption = f"{seq} [{row['frames']}f · {row['n_objects']}obj]"
432
+ else:
433
+ caption = f"{seq} [{row['frames']}f · {row['n_objects']}obj]"
434
+ items.append((img, caption))
435
  return items
436
 
437
 
 
754
  g_f_inputs = [g_year, g_split, g_obj, g_fmin, g_fmax, g_srch]
755
 
756
  def _on_g_filter(*args):
757
+ # args: *filter_fields, ov_thumb, sel_seqs
758
+ sel_seqs = list(args[-1])
759
+ ov = args[-2]
760
+ fargs = args[:-2]
761
+ fdf = filter_df(*fargs)
762
+ seqs = fdf["sequence"].tolist()
763
+ sel_set = set(sel_seqs)
764
+ items = build_gallery_items(
765
+ seqs, overlay=ov,
766
+ selected=sel_set, max_reached=(len(sel_seqs) == 4))
767
  return (items, seqs,
768
+ f"**{len(seqs)} sequences** — toggle to select (max 4)")
769
 
770
  for inp in g_f_inputs + [g_ov_th]:
771
+ inp.change(_on_g_filter, g_f_inputs + [g_ov_th, g_selected_state],
772
  [gallery, g_seq_state, g_count_md])
773
 
774
+ # ── Shared helpers ─────────────────────────────────────────
775
  def _build_video_updates(sel_seqs, ov, a, fps):
776
  """Return 4 gr.update() objects for video slots 0-3."""
777
  updates = []
 
788
  updates.append(gr.update(visible=False, value=None))
789
  return updates
790
 
791
+ def _rebuild_gallery(sel_seqs, g_seqs, ov_th):
792
+ sel_set = set(sel_seqs)
793
+ return build_gallery_items(
794
+ g_seqs, overlay=ov_th,
795
+ selected=sel_set, max_reached=(len(sel_seqs) == 4))
796
+
797
  # ── Gallery click → toggle selection ──────────────────────
798
  def _on_gallery_toggle(evt: gr.SelectData,
799
+ sel_seqs, g_seqs, ov, a, fps, ov_th):
800
  if evt is None or not g_seqs:
801
+ empty_gallery = _rebuild_gallery(sel_seqs, g_seqs, ov_th)
802
+ return (sel_seqs, empty_gallery,
803
  gr.update(visible=True), gr.update(visible=False),
804
  gr.update(visible=False, value=None),
805
  gr.update(visible=False, value=None),
 
808
  gr.update(visible=False))
809
 
810
  seq = g_seqs[evt.index]
811
+ max_before = len(sel_seqs) == 4
812
  if seq in sel_seqs:
813
  sel_seqs = [s for s in sel_seqs if s != seq]
814
+ elif not max_before:
815
  sel_seqs = sel_seqs + [seq]
816
+ # else: already at 4, ignoreglow state unchanged
817
 
818
  n = len(sel_seqs)
819
+ new_gallery = _rebuild_gallery(sel_seqs, g_seqs, ov_th)
820
  vid_updates = _build_video_updates(sel_seqs, ov, a, fps)
821
+
822
+ if n == 0:
823
+ info_txt = ""
824
+ elif n == 4:
825
+ info_txt = ("▶ " + " · ".join(f"**{s}**" for s in sel_seqs) +
826
+ " — **max 4 reached**, deselect to swap")
827
+ else:
828
+ info_txt = ("▶ " + " · ".join(f"**{s}**" for s in sel_seqs) +
829
+ " *(click a glowing thumbnail to deselect)*")
830
+
831
  return (
832
  sel_seqs,
833
+ new_gallery,
834
+ gr.update(visible=(n == 0)),
835
+ gr.update(visible=(n > 0), value=info_txt),
836
  vid_updates[0],
837
  vid_updates[1],
838
  vid_updates[2],
839
  vid_updates[3],
840
+ gr.update(visible=(n > 0)),
841
  )
842
 
843
  gallery.select(
844
  _on_gallery_toggle,
845
+ inputs=[g_selected_state, g_seq_state,
846
+ g_vid_ov, g_vid_a, g_fps, g_ov_th],
847
+ outputs=[g_selected_state, gallery,
848
+ g_placeholder, g_sel_info,
849
  g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
850
  )
851
 
 
861
  )
862
 
863
  # Clear selection button
864
+ def _clear_selection(g_seqs, ov_th):
865
+ fresh_gallery = build_gallery_items(g_seqs, overlay=ov_th)
866
  return (
867
  [],
868
+ fresh_gallery,
869
  gr.update(visible=True),
870
  gr.update(visible=False, value=""),
871
  gr.update(visible=False, value=None),
 
877
 
878
  g_clr_btn.click(
879
  _clear_selection,
880
+ inputs=[g_seq_state, g_ov_th],
881
+ outputs=[g_selected_state, gallery,
882
+ g_placeholder, g_sel_info,
883
  g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
884
  )
885