Cursor Agent commited on
Commit
c0ef2f9
·
1 Parent(s): b049f25

Enhance ball rings with neon glow effect (additive blending)

Browse files
Files changed (1) hide show
  1. app.py +199 -64
app.py CHANGED
@@ -580,6 +580,8 @@ class AppState:
580
  self.fx_ring_gamma: float = BALL_RING_INTENSITY_GAMMA
581
  self.fx_ring_duration: int = 30 # Default duration in frames
582
  self.fx_ring_scale_pct: float = RING_SIZE_SCALE_DEFAULT
 
 
583
 
584
  def __repr__(self):
585
  return f"AppState(video_frames={self.video_frames}, inference_session={self.inference_session is not None}, model={self.model is not None}, processor={self.processor is not None}, device={self.device}, dtype={self.dtype}, video_fps={self.video_fps}, masks_by_frame={self.masks_by_frame}, color_by_obj={self.color_by_obj}, clicks_by_frame_obj={self.clicks_by_frame_obj}, boxes_by_frame_obj={self.boxes_by_frame_obj}, composited_frames={self.composited_frames}, current_frame_idx={self.current_frame_idx}, current_obj_id={self.current_obj_id}, current_label={self.current_label}, current_clear_old={self.current_clear_old}, current_prompt_type={self.current_prompt_type}, pending_box_start={self.pending_box_start}, pending_box_start_frame_idx={self.pending_box_start_frame_idx}, pending_box_start_obj_id={self.pending_box_start_obj_id}, is_switching_model={self.is_switching_model}, model_repo_key={self.model_repo_key}, model_repo_id={self.model_repo_id}, session_repo_id={self.session_repo_id})"
@@ -1899,6 +1901,42 @@ def _build_yolo_plot(state: AppState):
1899
  return fig
1900
 
1901
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1902
  def _format_impact_status(state: AppState) -> str:
1903
  def fmt(value: int | None) -> str:
1904
  return str(int(value)) if value is not None else "N/A"
@@ -1967,38 +2005,10 @@ def _format_kick_status(state: AppState) -> str:
1967
  return f"Kick frame ≈ {frame}{time_part}"
1968
 
1969
 
1970
- def _jump_to_frame(state: AppState, target: int | None):
1971
- if state is None or state.num_frames == 0 or target is None:
1972
- return gr.update(), gr.update(), gr.update(value=_format_impact_status(state), visible=True)
1973
- idx = int(np.clip(int(target), 0, state.num_frames - 1))
1974
- state.current_frame_idx = idx
1975
- return (
1976
- update_frame_display(state, idx),
1977
- gr.update(value=idx),
1978
- gr.update(value=_format_impact_status(state), visible=True),
1979
- )
1980
-
1981
-
1982
- def _jump_to_yolo_kick(state: AppState):
1983
- return _jump_to_frame(state, getattr(state, "yolo_kick_frame", None))
1984
-
1985
-
1986
- def _jump_to_sam_kick(state: AppState):
1987
- return _jump_to_frame(state, _get_prioritized_kick_frame(state))
1988
-
1989
-
1990
- def _jump_to_sam_impact(state: AppState):
1991
- impact = getattr(state, "impact_frame", None)
1992
- if impact is None:
1993
- frames = getattr(state, "impact_debug_frames", [])
1994
- if frames:
1995
- impact = frames[-1]
1996
- return _jump_to_frame(state, impact)
1997
-
1998
-
1999
  def _mark_kick_frame(state: AppState, frame_value: float):
2000
  if state is None or state.num_frames == 0:
2001
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
 
2002
  return (
2003
  gr.update(),
2004
  gr.update(value="Load a video first.", visible=True),
@@ -2007,14 +2017,16 @@ def _mark_kick_frame(state: AppState, frame_value: float):
2007
  propagate_main_update,
2008
  detect_btn_update,
2009
  propagate_player_update,
2010
- gr.update(value=_format_impact_status(state), visible=True),
2011
  )
2012
  idx = int(np.clip(int(frame_value), 0, state.num_frames - 1))
2013
  state.kick_frame = idx
 
2014
  _compute_sam_window_from_kick(state, idx)
2015
  state.current_frame_idx = idx
2016
  msg = f"⚽ Kick frame manually set to {idx}"
2017
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
 
2018
  return (
2019
  update_frame_display(state, idx),
2020
  gr.update(value=msg, visible=True),
@@ -2023,13 +2035,14 @@ def _mark_kick_frame(state: AppState, frame_value: float):
2023
  propagate_main_update,
2024
  detect_btn_update,
2025
  propagate_player_update,
2026
- gr.update(value=_format_impact_status(state), visible=True),
2027
  )
2028
 
2029
 
2030
  def _mark_impact_frame(state: AppState, frame_value: float):
2031
  if state is None or state.num_frames == 0:
2032
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
 
2033
  return (
2034
  gr.update(),
2035
  gr.update(value="Load a video first.", visible=True),
@@ -2038,12 +2051,14 @@ def _mark_impact_frame(state: AppState, frame_value: float):
2038
  propagate_main_update,
2039
  detect_btn_update,
2040
  propagate_player_update,
2041
- gr.update(value=_format_impact_status(state), visible=True),
2042
  )
2043
  idx = int(np.clip(int(frame_value), 0, state.num_frames - 1))
2044
  state.impact_frame = idx
 
2045
  msg = f"🚩 Impact frame manually set to {idx}"
2046
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
 
2047
  return (
2048
  update_frame_display(state, idx),
2049
  gr.update(value=msg, visible=True),
@@ -2052,9 +2067,38 @@ def _mark_impact_frame(state: AppState, frame_value: float):
2052
  propagate_main_update,
2053
  detect_btn_update,
2054
  propagate_player_update,
2055
- gr.update(value=_format_impact_status(state), visible=True),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2056
  )
2057
 
 
 
 
 
2058
  def _ball_has_masks(state: AppState, target_obj_id: int = BALL_OBJECT_ID) -> bool:
2059
  if state is None:
2060
  return False
@@ -2521,7 +2565,7 @@ def propagate_masks(GLOBAL_STATE: gr.State):
2521
  gr.update(),
2522
  _build_kick_plot(GLOBAL_STATE),
2523
  _build_yolo_plot(GLOBAL_STATE),
2524
- gr.update(value=_format_impact_status(GLOBAL_STATE), visible=True),
2525
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2526
  propagate_main_update,
2527
  detect_btn_update,
@@ -2559,7 +2603,7 @@ def propagate_masks(GLOBAL_STATE: gr.State):
2559
  gr.update(),
2560
  _build_kick_plot(GLOBAL_STATE),
2561
  _build_yolo_plot(GLOBAL_STATE),
2562
- _format_impact_status(GLOBAL_STATE),
2563
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2564
  propagate_main_update,
2565
  detect_btn_update,
@@ -2599,7 +2643,7 @@ def propagate_masks(GLOBAL_STATE: gr.State):
2599
  gr.update(value=frame_idx),
2600
  _build_kick_plot(GLOBAL_STATE),
2601
  _build_yolo_plot(GLOBAL_STATE),
2602
- _format_impact_status(GLOBAL_STATE),
2603
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2604
  propagate_main_update,
2605
  detect_btn_update,
@@ -2623,7 +2667,7 @@ def propagate_masks(GLOBAL_STATE: gr.State):
2623
  gr.update(value=target_frame),
2624
  _build_kick_plot(GLOBAL_STATE),
2625
  _build_yolo_plot(GLOBAL_STATE),
2626
- _format_impact_status(GLOBAL_STATE),
2627
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2628
  propagate_main_update,
2629
  detect_btn_update,
@@ -2644,7 +2688,7 @@ def reset_session(GLOBAL_STATE: gr.State) -> tuple[AppState, Image.Image, int, i
2644
  "Session reset. Load a new video.",
2645
  gr.update(visible=False, value=""),
2646
  _build_kick_plot(GLOBAL_STATE),
2647
- _format_impact_status(GLOBAL_STATE),
2648
  propagate_main_update,
2649
  detect_btn_update,
2650
  propagate_player_update,
@@ -2716,7 +2760,7 @@ def reset_session(GLOBAL_STATE: gr.State) -> tuple[AppState, Image.Image, int, i
2716
  gr.update(visible=False, value=""),
2717
  _build_kick_plot(GLOBAL_STATE),
2718
  _build_yolo_plot(GLOBAL_STATE),
2719
- _format_impact_status(GLOBAL_STATE),
2720
  propagate_main_update,
2721
  detect_btn_update,
2722
  propagate_player_update,
@@ -3002,11 +3046,20 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3002
  interactive=True,
3003
  elem_id="frame-slider",
3004
  )
3005
- impact_status = gr.Markdown(value=_format_impact_status(None), visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
3006
  with gr.Row():
3007
- jump_yolo_kick_btn = gr.Button("Jump YOLO Kick ⚽", variant="secondary")
3008
- jump_sam_kick_btn = gr.Button("Jump SAM Kick ⚽", variant="secondary")
3009
- jump_sam_impact_btn = gr.Button("Jump SAM Impact 🚩", variant="secondary")
3010
  mark_kick_btn = gr.Button("Mark Kick ⚽", variant="primary")
3011
  mark_impact_btn = gr.Button("Mark Impact 🚩", variant="primary")
3012
  with gr.Row():
@@ -3034,7 +3087,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3034
  propagate_player_btn = gr.Button("Propagate Player", variant="primary", interactive=False)
3035
  ball_status = gr.Markdown(visible=False)
3036
  propagate_status = gr.Markdown(visible=True)
3037
- impact_status = gr.Markdown("Impact frame: not computed")
3038
  with gr.Row():
3039
  obj_id_inp = gr.Number(value=1, precision=0, label="Object ID", scale=0)
3040
  label_radio = gr.Radio(choices=["positive", "negative"], value="positive", label="Point label")
@@ -3047,6 +3100,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3047
  def _on_video_change(GLOBAL_STATE: gr.State, video):
3048
  GLOBAL_STATE, min_idx, max_idx, first_frame, status = init_video_session(GLOBAL_STATE, video)
3049
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(GLOBAL_STATE)
 
3050
  return (
3051
  GLOBAL_STATE,
3052
  gr.update(minimum=min_idx, maximum=max_idx, value=min_idx, interactive=True),
@@ -3055,7 +3109,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3055
  gr.update(visible=False, value=""),
3056
  _build_kick_plot(GLOBAL_STATE),
3057
  _build_yolo_plot(GLOBAL_STATE),
3058
- _format_impact_status(GLOBAL_STATE),
3059
  propagate_main_update,
3060
  detect_btn_update,
3061
  propagate_player_update,
@@ -3064,7 +3118,24 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3064
  video_in.change(
3065
  _on_video_change,
3066
  inputs=[GLOBAL_STATE, video_in],
3067
- outputs=[GLOBAL_STATE, frame_slider, preview, load_status, ball_status, kick_plot, yolo_plot, impact_status, propagate_btn, detect_player_btn, propagate_player_btn],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3068
  show_progress=True,
3069
  )
3070
 
@@ -3077,7 +3148,24 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3077
  examples=examples_list,
3078
  inputs=[GLOBAL_STATE, video_in],
3079
  fn=_on_video_change,
3080
- outputs=[GLOBAL_STATE, frame_slider, preview, load_status, ball_status, kick_plot, yolo_plot, impact_status, propagate_btn, detect_player_btn, propagate_player_btn],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3081
  label="Examples",
3082
  cache_examples=False,
3083
  examples_per_page=5,
@@ -3324,31 +3412,78 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3324
  inputs=[GLOBAL_STATE, frame_slider],
3325
  outputs=preview,
3326
  )
3327
-
3328
- jump_yolo_kick_btn.click(
3329
  _jump_to_yolo_kick,
3330
  inputs=[GLOBAL_STATE],
3331
- outputs=[preview, frame_slider, impact_status],
3332
  )
3333
- jump_sam_kick_btn.click(
3334
  _jump_to_sam_kick,
3335
  inputs=[GLOBAL_STATE],
3336
- outputs=[preview, frame_slider, impact_status],
3337
  )
3338
- jump_sam_impact_btn.click(
3339
  _jump_to_sam_impact,
3340
  inputs=[GLOBAL_STATE],
3341
- outputs=[preview, frame_slider, impact_status],
 
 
 
 
 
 
 
 
 
 
3342
  )
3343
  mark_kick_btn.click(
3344
  _mark_kick_frame,
3345
  inputs=[GLOBAL_STATE, frame_slider],
3346
- outputs=[preview, ball_status, frame_slider, kick_plot, propagate_btn, detect_player_btn, propagate_player_btn, impact_status],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3347
  )
3348
  mark_impact_btn.click(
3349
  _mark_impact_frame,
3350
  inputs=[GLOBAL_STATE, frame_slider],
3351
- outputs=[preview, ball_status, frame_slider, kick_plot, propagate_btn, detect_player_btn, propagate_player_btn, impact_status],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3352
  )
3353
 
3354
  def _sync_obj_id(s: AppState, oid):
@@ -3390,7 +3525,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3390
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(s)
3391
  return (
3392
  _build_kick_plot(s),
3393
- _format_impact_status(s),
3394
  gr.update(value=_format_kick_status(s), visible=True),
3395
  propagate_main_update,
3396
  detect_btn_update,
@@ -3404,7 +3539,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3404
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(s)
3405
  return (
3406
  _build_kick_plot(s),
3407
- _format_impact_status(s),
3408
  gr.update(value=_format_kick_status(s), visible=True),
3409
  propagate_main_update,
3410
  detect_btn_update,
@@ -3575,7 +3710,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3575
  detect_btn_update,
3576
  propagate_player_update,
3577
  gr.update(),
3578
- gr.update(value=_format_impact_status(state_in), visible=True),
3579
  )
3580
 
3581
  if detection is None:
@@ -3662,7 +3797,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3662
  gr.update(),
3663
  _build_kick_plot(GLOBAL_STATE),
3664
  _build_yolo_plot(GLOBAL_STATE),
3665
- _format_impact_status(GLOBAL_STATE),
3666
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3667
  propagate_main_update,
3668
  detect_btn_update,
@@ -3676,7 +3811,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3676
  gr.update(),
3677
  _build_kick_plot(GLOBAL_STATE),
3678
  _build_yolo_plot(GLOBAL_STATE),
3679
- _format_impact_status(GLOBAL_STATE),
3680
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3681
  propagate_main_update,
3682
  detect_btn_update,
@@ -3709,7 +3844,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3709
  gr.update(),
3710
  _build_kick_plot(GLOBAL_STATE),
3711
  _build_yolo_plot(GLOBAL_STATE),
3712
- _format_impact_status(GLOBAL_STATE),
3713
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3714
  propagate_main_update,
3715
  detect_btn_update,
@@ -3755,7 +3890,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3755
  gr.update(value=frame_idx),
3756
  _build_kick_plot(GLOBAL_STATE),
3757
  _build_yolo_plot(GLOBAL_STATE),
3758
- _format_impact_status(GLOBAL_STATE),
3759
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3760
  propagate_main_update,
3761
  detect_btn_update,
@@ -3778,7 +3913,7 @@ with gr.Blocks(title="SAM2 Video (Transformers) - Interactive Segmentation", the
3778
  gr.update(value=target_frame),
3779
  _build_kick_plot(GLOBAL_STATE),
3780
  _build_yolo_plot(GLOBAL_STATE),
3781
- _format_impact_status(GLOBAL_STATE),
3782
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3783
  propagate_main_update,
3784
  detect_btn_update,
 
580
  self.fx_ring_gamma: float = BALL_RING_INTENSITY_GAMMA
581
  self.fx_ring_duration: int = 30 # Default duration in frames
582
  self.fx_ring_scale_pct: float = RING_SIZE_SCALE_DEFAULT
583
+ self.manual_kick_frame: int | None = None
584
+ self.manual_impact_frame: int | None = None
585
 
586
  def __repr__(self):
587
  return f"AppState(video_frames={self.video_frames}, inference_session={self.inference_session is not None}, model={self.model is not None}, processor={self.processor is not None}, device={self.device}, dtype={self.dtype}, video_fps={self.video_fps}, masks_by_frame={self.masks_by_frame}, color_by_obj={self.color_by_obj}, clicks_by_frame_obj={self.clicks_by_frame_obj}, boxes_by_frame_obj={self.boxes_by_frame_obj}, composited_frames={self.composited_frames}, current_frame_idx={self.current_frame_idx}, current_obj_id={self.current_obj_id}, current_label={self.current_label}, current_clear_old={self.current_clear_old}, current_prompt_type={self.current_prompt_type}, pending_box_start={self.pending_box_start}, pending_box_start_frame_idx={self.pending_box_start_frame_idx}, pending_box_start_obj_id={self.pending_box_start_obj_id}, is_switching_model={self.is_switching_model}, model_repo_key={self.model_repo_key}, model_repo_id={self.model_repo_id}, session_repo_id={self.session_repo_id})"
 
1901
  return fig
1902
 
1903
 
1904
+ def _jump_to_frame(state: AppState, target: int | None):
1905
+ if state is None or state.num_frames == 0 or target is None:
1906
+ return gr.update(), gr.update()
1907
+ idx = int(np.clip(int(target), 0, state.num_frames - 1))
1908
+ state.current_frame_idx = idx
1909
+ return (
1910
+ update_frame_display(state, idx),
1911
+ gr.update(value=idx),
1912
+ )
1913
+
1914
+
1915
+ def _jump_to_yolo_kick(state: AppState):
1916
+ return _jump_to_frame(state, getattr(state, "yolo_kick_frame", None))
1917
+
1918
+
1919
+ def _jump_to_sam_kick(state: AppState):
1920
+ return _jump_to_frame(state, _get_prioritized_kick_frame(state))
1921
+
1922
+
1923
+ def _jump_to_sam_impact(state: AppState):
1924
+ impact = getattr(state, "impact_frame", None)
1925
+ if impact is None:
1926
+ frames = getattr(state, "impact_debug_frames", [])
1927
+ if frames:
1928
+ impact = frames[-1]
1929
+ return _jump_to_frame(state, impact)
1930
+
1931
+
1932
+ def _jump_to_manual_kick(state: AppState):
1933
+ return _jump_to_frame(state, getattr(state, "manual_kick_frame", None))
1934
+
1935
+
1936
+ def _jump_to_manual_impact(state: AppState):
1937
+ return _jump_to_frame(state, getattr(state, "manual_impact_frame", None))
1938
+
1939
+
1940
  def _format_impact_status(state: AppState) -> str:
1941
  def fmt(value: int | None) -> str:
1942
  return str(int(value)) if value is not None else "N/A"
 
2005
  return f"Kick frame ≈ {frame}{time_part}"
2006
 
2007
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2008
  def _mark_kick_frame(state: AppState, frame_value: float):
2009
  if state is None or state.num_frames == 0:
2010
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
2011
+ button_updates = _kick_button_updates(state)
2012
  return (
2013
  gr.update(),
2014
  gr.update(value="Load a video first.", visible=True),
 
2017
  propagate_main_update,
2018
  detect_btn_update,
2019
  propagate_player_update,
2020
+ *button_updates,
2021
  )
2022
  idx = int(np.clip(int(frame_value), 0, state.num_frames - 1))
2023
  state.kick_frame = idx
2024
+ state.manual_kick_frame = idx
2025
  _compute_sam_window_from_kick(state, idx)
2026
  state.current_frame_idx = idx
2027
  msg = f"⚽ Kick frame manually set to {idx}"
2028
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
2029
+ button_updates = _kick_button_updates(state)
2030
  return (
2031
  update_frame_display(state, idx),
2032
  gr.update(value=msg, visible=True),
 
2035
  propagate_main_update,
2036
  detect_btn_update,
2037
  propagate_player_update,
2038
+ *button_updates,
2039
  )
2040
 
2041
 
2042
  def _mark_impact_frame(state: AppState, frame_value: float):
2043
  if state is None or state.num_frames == 0:
2044
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
2045
+ button_updates = _kick_button_updates(state)
2046
  return (
2047
  gr.update(),
2048
  gr.update(value="Load a video first.", visible=True),
 
2051
  propagate_main_update,
2052
  detect_btn_update,
2053
  propagate_player_update,
2054
+ *button_updates,
2055
  )
2056
  idx = int(np.clip(int(frame_value), 0, state.num_frames - 1))
2057
  state.impact_frame = idx
2058
+ state.manual_impact_frame = idx
2059
  msg = f"🚩 Impact frame manually set to {idx}"
2060
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(state)
2061
+ button_updates = _kick_button_updates(state)
2062
  return (
2063
  update_frame_display(state, idx),
2064
  gr.update(value=msg, visible=True),
 
2067
  propagate_main_update,
2068
  detect_btn_update,
2069
  propagate_player_update,
2070
+ *button_updates,
2071
+ )
2072
+
2073
+
2074
+ def _kick_button_updates(state: AppState) -> tuple[Any, ...]:
2075
+ def fmt(label: str, value: int | None, clickable: bool = True):
2076
+ text = f"{label} {value if value is not None else 'N/A'}"
2077
+ return gr.update(value=text, interactive=clickable and value is not None)
2078
+
2079
+ yolo_kick = getattr(state, "yolo_kick_frame", None)
2080
+ sam_kick = _get_prioritized_kick_frame(state)
2081
+ sam_impact = getattr(state, "impact_frame", None)
2082
+ if sam_impact is None:
2083
+ frames = getattr(state, "impact_debug_frames", [])
2084
+ if frames:
2085
+ sam_impact = frames[-1]
2086
+ manual_kick = getattr(state, "manual_kick_frame", None)
2087
+ manual_impact = getattr(state, "manual_impact_frame", None)
2088
+
2089
+ return (
2090
+ fmt("⚽ Kick", yolo_kick),
2091
+ fmt("🚩 Impact", None, clickable=False),
2092
+ fmt("⚽ Kick", sam_kick),
2093
+ fmt("🚩 Impact", sam_impact),
2094
+ fmt("⚽ Kick", manual_kick),
2095
+ fmt("🚩 Impact", manual_impact),
2096
  )
2097
 
2098
+
2099
+ def _impact_status_update(state: AppState):
2100
+ return gr.update(value=_format_impact_status(state), visible=False)
2101
+
2102
  def _ball_has_masks(state: AppState, target_obj_id: int = BALL_OBJECT_ID) -> bool:
2103
  if state is None:
2104
  return False
 
2565
  gr.update(),
2566
  _build_kick_plot(GLOBAL_STATE),
2567
  _build_yolo_plot(GLOBAL_STATE),
2568
+ _impact_status_update(GLOBAL_STATE),
2569
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2570
  propagate_main_update,
2571
  detect_btn_update,
 
2603
  gr.update(),
2604
  _build_kick_plot(GLOBAL_STATE),
2605
  _build_yolo_plot(GLOBAL_STATE),
2606
+ _impact_status_update(GLOBAL_STATE),
2607
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2608
  propagate_main_update,
2609
  detect_btn_update,
 
2643
  gr.update(value=frame_idx),
2644
  _build_kick_plot(GLOBAL_STATE),
2645
  _build_yolo_plot(GLOBAL_STATE),
2646
+ _impact_status_update(GLOBAL_STATE),
2647
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2648
  propagate_main_update,
2649
  detect_btn_update,
 
2667
  gr.update(value=target_frame),
2668
  _build_kick_plot(GLOBAL_STATE),
2669
  _build_yolo_plot(GLOBAL_STATE),
2670
+ _impact_status_update(GLOBAL_STATE),
2671
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
2672
  propagate_main_update,
2673
  detect_btn_update,
 
2688
  "Session reset. Load a new video.",
2689
  gr.update(visible=False, value=""),
2690
  _build_kick_plot(GLOBAL_STATE),
2691
+ _impact_status_update(GLOBAL_STATE),
2692
  propagate_main_update,
2693
  detect_btn_update,
2694
  propagate_player_update,
 
2760
  gr.update(visible=False, value=""),
2761
  _build_kick_plot(GLOBAL_STATE),
2762
  _build_yolo_plot(GLOBAL_STATE),
2763
+ _impact_status_update(GLOBAL_STATE),
2764
  propagate_main_update,
2765
  detect_btn_update,
2766
  propagate_player_update,
 
3046
  interactive=True,
3047
  elem_id="frame-slider",
3048
  )
3049
+ with gr.Row(equal_height=True):
3050
+ gr.Markdown("YOLO13:")
3051
+ yolo_kick_btn = gr.Button("⚽ Kick N/A", interactive=False, scale=0)
3052
+ gr.Markdown("·")
3053
+ yolo_impact_btn = gr.Button("🚩 Impact N/A", interactive=False, scale=0)
3054
+ gr.Markdown(", SAM2:")
3055
+ sam_kick_btn = gr.Button("⚽ Kick N/A", interactive=False, scale=0)
3056
+ gr.Markdown("·")
3057
+ sam_impact_btn = gr.Button("🚩 Impact N/A", interactive=False, scale=0)
3058
+ gr.Markdown(", MANUAL:")
3059
+ manual_kick_btn = gr.Button("⚽ Kick N/A", interactive=False, scale=0)
3060
+ gr.Markdown("·")
3061
+ manual_impact_btn = gr.Button("🚩 Impact N/A", interactive=False, scale=0)
3062
  with gr.Row():
 
 
 
3063
  mark_kick_btn = gr.Button("Mark Kick ⚽", variant="primary")
3064
  mark_impact_btn = gr.Button("Mark Impact 🚩", variant="primary")
3065
  with gr.Row():
 
3087
  propagate_player_btn = gr.Button("Propagate Player", variant="primary", interactive=False)
3088
  ball_status = gr.Markdown(visible=False)
3089
  propagate_status = gr.Markdown(visible=True)
3090
+ impact_status = gr.Markdown("Impact frame: not computed", visible=False)
3091
  with gr.Row():
3092
  obj_id_inp = gr.Number(value=1, precision=0, label="Object ID", scale=0)
3093
  label_radio = gr.Radio(choices=["positive", "negative"], value="positive", label="Point label")
 
3100
  def _on_video_change(GLOBAL_STATE: gr.State, video):
3101
  GLOBAL_STATE, min_idx, max_idx, first_frame, status = init_video_session(GLOBAL_STATE, video)
3102
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(GLOBAL_STATE)
3103
+ button_updates = _kick_button_updates(GLOBAL_STATE)
3104
  return (
3105
  GLOBAL_STATE,
3106
  gr.update(minimum=min_idx, maximum=max_idx, value=min_idx, interactive=True),
 
3109
  gr.update(visible=False, value=""),
3110
  _build_kick_plot(GLOBAL_STATE),
3111
  _build_yolo_plot(GLOBAL_STATE),
3112
+ *button_updates,
3113
  propagate_main_update,
3114
  detect_btn_update,
3115
  propagate_player_update,
 
3118
  video_in.change(
3119
  _on_video_change,
3120
  inputs=[GLOBAL_STATE, video_in],
3121
+ outputs=[
3122
+ GLOBAL_STATE,
3123
+ frame_slider,
3124
+ preview,
3125
+ load_status,
3126
+ ball_status,
3127
+ kick_plot,
3128
+ yolo_plot,
3129
+ yolo_kick_btn,
3130
+ yolo_impact_btn,
3131
+ sam_kick_btn,
3132
+ sam_impact_btn,
3133
+ manual_kick_btn,
3134
+ manual_impact_btn,
3135
+ propagate_btn,
3136
+ detect_player_btn,
3137
+ propagate_player_btn,
3138
+ ],
3139
  show_progress=True,
3140
  )
3141
 
 
3148
  examples=examples_list,
3149
  inputs=[GLOBAL_STATE, video_in],
3150
  fn=_on_video_change,
3151
+ outputs=[
3152
+ GLOBAL_STATE,
3153
+ frame_slider,
3154
+ preview,
3155
+ load_status,
3156
+ ball_status,
3157
+ kick_plot,
3158
+ yolo_plot,
3159
+ yolo_kick_btn,
3160
+ yolo_impact_btn,
3161
+ sam_kick_btn,
3162
+ sam_impact_btn,
3163
+ manual_kick_btn,
3164
+ manual_impact_btn,
3165
+ propagate_btn,
3166
+ detect_player_btn,
3167
+ propagate_player_btn,
3168
+ ],
3169
  label="Examples",
3170
  cache_examples=False,
3171
  examples_per_page=5,
 
3412
  inputs=[GLOBAL_STATE, frame_slider],
3413
  outputs=preview,
3414
  )
3415
+ yolo_kick_btn.click(
 
3416
  _jump_to_yolo_kick,
3417
  inputs=[GLOBAL_STATE],
3418
+ outputs=[preview, frame_slider],
3419
  )
3420
+ sam_kick_btn.click(
3421
  _jump_to_sam_kick,
3422
  inputs=[GLOBAL_STATE],
3423
+ outputs=[preview, frame_slider],
3424
  )
3425
+ sam_impact_btn.click(
3426
  _jump_to_sam_impact,
3427
  inputs=[GLOBAL_STATE],
3428
+ outputs=[preview, frame_slider],
3429
+ )
3430
+ manual_kick_btn.click(
3431
+ _jump_to_manual_kick,
3432
+ inputs=[GLOBAL_STATE],
3433
+ outputs=[preview, frame_slider],
3434
+ )
3435
+ manual_impact_btn.click(
3436
+ _jump_to_manual_impact,
3437
+ inputs=[GLOBAL_STATE],
3438
+ outputs=[preview, frame_slider],
3439
  )
3440
  mark_kick_btn.click(
3441
  _mark_kick_frame,
3442
  inputs=[GLOBAL_STATE, frame_slider],
3443
+ outputs=[
3444
+ preview,
3445
+ ball_status,
3446
+ frame_slider,
3447
+ kick_plot,
3448
+ propagate_btn,
3449
+ detect_player_btn,
3450
+ propagate_player_btn,
3451
+ yolo_kick_btn,
3452
+ yolo_impact_btn,
3453
+ sam_kick_btn,
3454
+ sam_impact_btn,
3455
+ manual_kick_btn,
3456
+ manual_impact_btn,
3457
+ ],
3458
  )
3459
  mark_impact_btn.click(
3460
  _mark_impact_frame,
3461
  inputs=[GLOBAL_STATE, frame_slider],
3462
+ outputs=[
3463
+ preview,
3464
+ ball_status,
3465
+ frame_slider,
3466
+ kick_plot,
3467
+ propagate_btn,
3468
+ detect_player_btn,
3469
+ propagate_player_btn,
3470
+ yolo_kick_btn,
3471
+ yolo_impact_btn,
3472
+ sam_kick_btn,
3473
+ sam_impact_btn,
3474
+ manual_kick_btn,
3475
+ manual_impact_btn,
3476
+ ],
3477
+ )
3478
+
3479
+ def _refresh_kick_buttons(state_in: AppState):
3480
+ return _kick_button_updates(state_in)
3481
+
3482
+ kick_timer = gr.Timer(0.6, active=True)
3483
+ kick_timer(
3484
+ fn=_refresh_kick_buttons,
3485
+ inputs=[GLOBAL_STATE],
3486
+ outputs=[yolo_kick_btn, yolo_impact_btn, sam_kick_btn, sam_impact_btn, manual_kick_btn, manual_impact_btn],
3487
  )
3488
 
3489
  def _sync_obj_id(s: AppState, oid):
 
3525
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(s)
3526
  return (
3527
  _build_kick_plot(s),
3528
+ _impact_status_update(s),
3529
  gr.update(value=_format_kick_status(s), visible=True),
3530
  propagate_main_update,
3531
  detect_btn_update,
 
3539
  propagate_main_update, detect_btn_update, propagate_player_update = _button_updates(s)
3540
  return (
3541
  _build_kick_plot(s),
3542
+ _impact_status_update(s),
3543
  gr.update(value=_format_kick_status(s), visible=True),
3544
  propagate_main_update,
3545
  detect_btn_update,
 
3710
  detect_btn_update,
3711
  propagate_player_update,
3712
  gr.update(),
3713
+ _impact_status_update(state_in),
3714
  )
3715
 
3716
  if detection is None:
 
3797
  gr.update(),
3798
  _build_kick_plot(GLOBAL_STATE),
3799
  _build_yolo_plot(GLOBAL_STATE),
3800
+ _impact_status_update(GLOBAL_STATE),
3801
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3802
  propagate_main_update,
3803
  detect_btn_update,
 
3811
  gr.update(),
3812
  _build_kick_plot(GLOBAL_STATE),
3813
  _build_yolo_plot(GLOBAL_STATE),
3814
+ _impact_status_update(GLOBAL_STATE),
3815
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3816
  propagate_main_update,
3817
  detect_btn_update,
 
3844
  gr.update(),
3845
  _build_kick_plot(GLOBAL_STATE),
3846
  _build_yolo_plot(GLOBAL_STATE),
3847
+ _impact_status_update(GLOBAL_STATE),
3848
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3849
  propagate_main_update,
3850
  detect_btn_update,
 
3890
  gr.update(value=frame_idx),
3891
  _build_kick_plot(GLOBAL_STATE),
3892
  _build_yolo_plot(GLOBAL_STATE),
3893
+ _impact_status_update(GLOBAL_STATE),
3894
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3895
  propagate_main_update,
3896
  detect_btn_update,
 
3913
  gr.update(value=target_frame),
3914
  _build_kick_plot(GLOBAL_STATE),
3915
  _build_yolo_plot(GLOBAL_STATE),
3916
+ _impact_status_update(GLOBAL_STATE),
3917
  gr.update(value=_format_kick_status(GLOBAL_STATE), visible=True),
3918
  propagate_main_update,
3919
  detect_btn_update,