Cursor Agent
commited on
Commit
·
c0ef2f9
1
Parent(s):
b049f25
Enhance ball rings with neon glow effect (additive blending)
Browse files
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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 3332 |
)
|
| 3333 |
-
|
| 3334 |
_jump_to_sam_kick,
|
| 3335 |
inputs=[GLOBAL_STATE],
|
| 3336 |
-
outputs=[preview, frame_slider
|
| 3337 |
)
|
| 3338 |
-
|
| 3339 |
_jump_to_sam_impact,
|
| 3340 |
inputs=[GLOBAL_STATE],
|
| 3341 |
-
outputs=[preview, frame_slider
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3342 |
)
|
| 3343 |
mark_kick_btn.click(
|
| 3344 |
_mark_kick_frame,
|
| 3345 |
inputs=[GLOBAL_STATE, frame_slider],
|
| 3346 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3347 |
)
|
| 3348 |
mark_impact_btn.click(
|
| 3349 |
_mark_impact_frame,
|
| 3350 |
inputs=[GLOBAL_STATE, frame_slider],
|
| 3351 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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,
|