Cursor Agent
commited on
Commit
·
8d53af3
1
Parent(s):
29a3083
Enhance ball rings with neon glow effect (additive blending)
Browse files
app.py
CHANGED
|
@@ -8,7 +8,6 @@ import math
|
|
| 8 |
import statistics
|
| 9 |
from pathlib import Path
|
| 10 |
import json
|
| 11 |
-
from functools import partial
|
| 12 |
|
| 13 |
import plotly.graph_objects as go
|
| 14 |
BASE64_VIDEO_PATH = Path("Kickit-Video-2025-07-09-13-47-18-389.b64")
|
|
@@ -3097,60 +3096,6 @@ CUSTOM_CSS = """
|
|
| 3097 |
width: fit-content;
|
| 3098 |
padding: 0.25rem 0.55rem;
|
| 3099 |
}
|
| 3100 |
-
.kt-info-btn button {
|
| 3101 |
-
border-radius: 9999px;
|
| 3102 |
-
width: 32px;
|
| 3103 |
-
height: 32px;
|
| 3104 |
-
padding: 0;
|
| 3105 |
-
min-width: 32px;
|
| 3106 |
-
background: #475569;
|
| 3107 |
-
border: 1px solid #1f2937;
|
| 3108 |
-
color: #fff;
|
| 3109 |
-
font-weight: 700;
|
| 3110 |
-
font-size: 0.85rem;
|
| 3111 |
-
}
|
| 3112 |
-
.kt-info-btn button:hover {
|
| 3113 |
-
background: #334155;
|
| 3114 |
-
}
|
| 3115 |
-
.kt-info-close-trigger {
|
| 3116 |
-
display: none !important;
|
| 3117 |
-
}
|
| 3118 |
-
.kt-info-overlay {
|
| 3119 |
-
position: fixed;
|
| 3120 |
-
inset: 0;
|
| 3121 |
-
background: rgba(0, 0, 0, 0.65);
|
| 3122 |
-
display: none;
|
| 3123 |
-
align-items: center;
|
| 3124 |
-
justify-content: center;
|
| 3125 |
-
z-index: 9999;
|
| 3126 |
-
padding: 1.5rem;
|
| 3127 |
-
}
|
| 3128 |
-
.kt-info-overlay.active {
|
| 3129 |
-
display: flex;
|
| 3130 |
-
}
|
| 3131 |
-
.kt-info-card {
|
| 3132 |
-
background: #111827;
|
| 3133 |
-
border: 1px solid #374151;
|
| 3134 |
-
border-radius: 0.65rem;
|
| 3135 |
-
max-width: 520px;
|
| 3136 |
-
width: 100%;
|
| 3137 |
-
padding: 1.2rem 1.4rem;
|
| 3138 |
-
color: #f3f4f6;
|
| 3139 |
-
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.45);
|
| 3140 |
-
position: relative;
|
| 3141 |
-
font-size: 0.95rem;
|
| 3142 |
-
line-height: 1.5;
|
| 3143 |
-
}
|
| 3144 |
-
.kt-info-close {
|
| 3145 |
-
position: absolute;
|
| 3146 |
-
top: 0.6rem;
|
| 3147 |
-
right: 0.6rem;
|
| 3148 |
-
border: none;
|
| 3149 |
-
background: transparent;
|
| 3150 |
-
color: #f87171;
|
| 3151 |
-
font-size: 1.3rem;
|
| 3152 |
-
cursor: pointer;
|
| 3153 |
-
}
|
| 3154 |
"""
|
| 3155 |
|
| 3156 |
BUTTON_TOOLTIPS = {
|
|
@@ -3192,41 +3137,12 @@ BUTTON_TOOLTIPS = {
|
|
| 3192 |
),
|
| 3193 |
}
|
| 3194 |
|
| 3195 |
-
|
| 3196 |
-
|
| 3197 |
-
|
| 3198 |
-
|
| 3199 |
-
|
| 3200 |
-
|
| 3201 |
-
"btn-track-ball-yolo": "YOLO13 · Track Ball",
|
| 3202 |
-
"btn-detect-player": "YOLO13 · Detect Player",
|
| 3203 |
-
"btn-track-ball-sam": "SAM2 · Track Ball",
|
| 3204 |
-
"btn-track-player-sam": "SAM2 · Track Player",
|
| 3205 |
-
}
|
| 3206 |
-
|
| 3207 |
-
|
| 3208 |
-
def _build_info_overlay_html(title: str, body: str) -> str:
|
| 3209 |
-
safe_title = title or "Workflow detail"
|
| 3210 |
-
safe_body = body.replace("\n", "<br/>")
|
| 3211 |
-
return f"""
|
| 3212 |
-
<div class="kt-info-overlay active" onclick="document.getElementById('kt-info-overlay-close').click();">
|
| 3213 |
-
<div class="kt-info-card" onclick="event.stopPropagation();">
|
| 3214 |
-
<button class="kt-info-close" onclick="document.getElementById('kt-info-overlay-close').click(); return false;">×</button>
|
| 3215 |
-
<h3 style="margin-top:0;">{safe_title}</h3>
|
| 3216 |
-
<p>{safe_body}</p>
|
| 3217 |
-
</div>
|
| 3218 |
-
</div>
|
| 3219 |
-
"""
|
| 3220 |
-
|
| 3221 |
-
|
| 3222 |
-
def _open_info(topic: str) -> Any:
|
| 3223 |
-
text = BUTTON_TOOLTIPS.get(topic, "More details coming soon.")
|
| 3224 |
-
title = BUTTON_TITLES.get(topic, "Workflow detail")
|
| 3225 |
-
return gr.update(value=_build_info_overlay_html(title, text), visible=True)
|
| 3226 |
-
|
| 3227 |
-
|
| 3228 |
-
def _close_info() -> Any:
|
| 3229 |
-
return gr.update(value="", visible=False)
|
| 3230 |
|
| 3231 |
with gr.Blocks(
|
| 3232 |
title="SAM2 Video (Transformers) - Interactive Segmentation",
|
|
@@ -3282,7 +3198,15 @@ with gr.Blocks(
|
|
| 3282 |
load_status = gr.Markdown(visible=True)
|
| 3283 |
with gr.Row():
|
| 3284 |
reset_btn = gr.Button("Reset Session", variant="secondary", elem_id="btn-reset-session")
|
| 3285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3286 |
with gr.Column(scale=1):
|
| 3287 |
gr.Markdown("**Preview**")
|
| 3288 |
preview = gr.Image(
|
|
@@ -3306,31 +3230,47 @@ with gr.Blocks(
|
|
| 3306 |
gr.Markdown("Manual", elem_classes=["model-label"])
|
| 3307 |
with gr.Row(elem_classes=["model-actions"]):
|
| 3308 |
mark_kick_btn = gr.Button("⚽ Mark Kick", variant="primary", elem_id="btn-mark-kick")
|
| 3309 |
-
mark_kick_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3310 |
mark_impact_btn = gr.Button("🚩 Mark Impact", variant="primary", elem_id="btn-mark-impact")
|
| 3311 |
-
mark_impact_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3312 |
with gr.Row(elem_classes=["model-status"]):
|
| 3313 |
manual_kick_btn = gr.Button("⚽: N/A", interactive=False)
|
| 3314 |
manual_impact_btn = gr.Button("🚩: N/A", interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3315 |
with gr.Column(elem_classes=["model-section"]):
|
| 3316 |
with gr.Row(elem_classes=["model-row"]):
|
| 3317 |
gr.Markdown("YOLO13", elem_classes=["model-label"])
|
| 3318 |
with gr.Row(elem_classes=["model-actions"]):
|
| 3319 |
detect_ball_btn = gr.Button("Detect Ball", variant="stop", elem_id="btn-detect-ball")
|
| 3320 |
-
detect_ball_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3321 |
track_ball_yolo_btn = gr.Button("Track Ball", variant="stop", elem_id="btn-track-ball-yolo")
|
| 3322 |
-
track_ball_yolo_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3323 |
detect_player_btn = gr.Button(
|
| 3324 |
"Detect Player",
|
| 3325 |
variant="stop",
|
| 3326 |
interactive=False,
|
| 3327 |
elem_id="btn-detect-player",
|
| 3328 |
)
|
| 3329 |
-
detect_player_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3330 |
with gr.Row(elem_classes=["model-status"]):
|
| 3331 |
yolo_kick_btn = gr.Button("⚽: N/A", interactive=False)
|
| 3332 |
yolo_impact_btn = gr.Button("🚩: N/A", interactive=False)
|
| 3333 |
yolo_plot = gr.Plot(label="YOLO kick diagnostics", show_label=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3334 |
with gr.Column(elem_classes=["model-section"]):
|
| 3335 |
with gr.Row(elem_classes=["model-row"]):
|
| 3336 |
gr.Markdown("SAM2", elem_classes=["model-label"])
|
|
@@ -3338,24 +3278,26 @@ with gr.Blocks(
|
|
| 3338 |
propagate_btn = gr.Button(
|
| 3339 |
"Track Ball", variant="stop", interactive=False, elem_id="btn-track-ball-sam"
|
| 3340 |
)
|
| 3341 |
-
propagate_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3342 |
propagate_player_btn = gr.Button(
|
| 3343 |
"Track Player",
|
| 3344 |
variant="stop",
|
| 3345 |
interactive=False,
|
| 3346 |
elem_id="btn-track-player-sam",
|
| 3347 |
)
|
| 3348 |
-
propagate_player_info_btn = gr.Button("ℹ️", elem_classes=["kt-info-btn"], scale=0)
|
| 3349 |
with gr.Row(elem_classes=["model-status"]):
|
| 3350 |
sam_kick_btn = gr.Button("⚽: N/A", interactive=False)
|
| 3351 |
sam_impact_btn = gr.Button("🚩: N/A", interactive=False)
|
| 3352 |
kick_plot = gr.Plot(label="Kick & impact diagnostics", show_label=True)
|
| 3353 |
-
|
| 3354 |
-
|
| 3355 |
-
|
| 3356 |
-
|
| 3357 |
-
|
| 3358 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3359 |
with gr.Row():
|
| 3360 |
min_impact_speed_slider = gr.Slider(
|
| 3361 |
label="Min impact speed (km/h)",
|
|
@@ -3426,20 +3368,6 @@ with gr.Blocks(
|
|
| 3426 |
show_progress=True,
|
| 3427 |
)
|
| 3428 |
|
| 3429 |
-
# Info overlay wiring
|
| 3430 |
-
def _bind_info(button, topic):
|
| 3431 |
-
button.click(partial(_open_info, topic), outputs=[info_overlay], queue=False)
|
| 3432 |
-
|
| 3433 |
-
_bind_info(reset_info_btn, "btn-reset-session")
|
| 3434 |
-
_bind_info(mark_kick_info_btn, "btn-mark-kick")
|
| 3435 |
-
_bind_info(mark_impact_info_btn, "btn-mark-impact")
|
| 3436 |
-
_bind_info(detect_ball_info_btn, "btn-detect-ball")
|
| 3437 |
-
_bind_info(track_ball_yolo_info_btn, "btn-track-ball-yolo")
|
| 3438 |
-
_bind_info(detect_player_info_btn, "btn-detect-player")
|
| 3439 |
-
_bind_info(propagate_info_btn, "btn-track-ball-sam")
|
| 3440 |
-
_bind_info(propagate_player_info_btn, "btn-track-player-sam")
|
| 3441 |
-
info_close_btn.click(_close_info, outputs=[info_overlay], queue=False)
|
| 3442 |
-
|
| 3443 |
example_video_path = ensure_example_video()
|
| 3444 |
examples_list = [
|
| 3445 |
[None, example_video_path],
|
|
|
|
| 8 |
import statistics
|
| 9 |
from pathlib import Path
|
| 10 |
import json
|
|
|
|
| 11 |
|
| 12 |
import plotly.graph_objects as go
|
| 13 |
BASE64_VIDEO_PATH = Path("Kickit-Video-2025-07-09-13-47-18-389.b64")
|
|
|
|
| 3096 |
width: fit-content;
|
| 3097 |
padding: 0.25rem 0.55rem;
|
| 3098 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3099 |
"""
|
| 3100 |
|
| 3101 |
BUTTON_TOOLTIPS = {
|
|
|
|
| 3137 |
),
|
| 3138 |
}
|
| 3139 |
|
| 3140 |
+
def _section_info_markdown(section_label: str, entries: list[tuple[str, str]]) -> str:
|
| 3141 |
+
chunks = [f"### {section_label}"]
|
| 3142 |
+
for label, key in entries:
|
| 3143 |
+
desc = BUTTON_TOOLTIPS.get(key, "")
|
| 3144 |
+
chunks.append(f"**{label}**\n{desc}")
|
| 3145 |
+
return "\n\n".join(chunks)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3146 |
|
| 3147 |
with gr.Blocks(
|
| 3148 |
title="SAM2 Video (Transformers) - Interactive Segmentation",
|
|
|
|
| 3198 |
load_status = gr.Markdown(visible=True)
|
| 3199 |
with gr.Row():
|
| 3200 |
reset_btn = gr.Button("Reset Session", variant="secondary", elem_id="btn-reset-session")
|
| 3201 |
+
with gr.Accordion("Session reset · details", open=False):
|
| 3202 |
+
gr.Markdown(
|
| 3203 |
+
_section_info_markdown(
|
| 3204 |
+
"Reset workflow",
|
| 3205 |
+
[
|
| 3206 |
+
("Reset Session", "btn-reset-session"),
|
| 3207 |
+
],
|
| 3208 |
+
)
|
| 3209 |
+
)
|
| 3210 |
with gr.Column(scale=1):
|
| 3211 |
gr.Markdown("**Preview**")
|
| 3212 |
preview = gr.Image(
|
|
|
|
| 3230 |
gr.Markdown("Manual", elem_classes=["model-label"])
|
| 3231 |
with gr.Row(elem_classes=["model-actions"]):
|
| 3232 |
mark_kick_btn = gr.Button("⚽ Mark Kick", variant="primary", elem_id="btn-mark-kick")
|
|
|
|
| 3233 |
mark_impact_btn = gr.Button("🚩 Mark Impact", variant="primary", elem_id="btn-mark-impact")
|
|
|
|
| 3234 |
with gr.Row(elem_classes=["model-status"]):
|
| 3235 |
manual_kick_btn = gr.Button("⚽: N/A", interactive=False)
|
| 3236 |
manual_impact_btn = gr.Button("🚩: N/A", interactive=False)
|
| 3237 |
+
with gr.Accordion("Manual controls · details", open=False):
|
| 3238 |
+
gr.Markdown(
|
| 3239 |
+
_section_info_markdown(
|
| 3240 |
+
"Manual controls",
|
| 3241 |
+
[
|
| 3242 |
+
("⚽ Mark Kick", "btn-mark-kick"),
|
| 3243 |
+
("🚩 Mark Impact", "btn-mark-impact"),
|
| 3244 |
+
],
|
| 3245 |
+
)
|
| 3246 |
+
)
|
| 3247 |
with gr.Column(elem_classes=["model-section"]):
|
| 3248 |
with gr.Row(elem_classes=["model-row"]):
|
| 3249 |
gr.Markdown("YOLO13", elem_classes=["model-label"])
|
| 3250 |
with gr.Row(elem_classes=["model-actions"]):
|
| 3251 |
detect_ball_btn = gr.Button("Detect Ball", variant="stop", elem_id="btn-detect-ball")
|
|
|
|
| 3252 |
track_ball_yolo_btn = gr.Button("Track Ball", variant="stop", elem_id="btn-track-ball-yolo")
|
|
|
|
| 3253 |
detect_player_btn = gr.Button(
|
| 3254 |
"Detect Player",
|
| 3255 |
variant="stop",
|
| 3256 |
interactive=False,
|
| 3257 |
elem_id="btn-detect-player",
|
| 3258 |
)
|
|
|
|
| 3259 |
with gr.Row(elem_classes=["model-status"]):
|
| 3260 |
yolo_kick_btn = gr.Button("⚽: N/A", interactive=False)
|
| 3261 |
yolo_impact_btn = gr.Button("🚩: N/A", interactive=False)
|
| 3262 |
yolo_plot = gr.Plot(label="YOLO kick diagnostics", show_label=True)
|
| 3263 |
+
with gr.Accordion("YOLO13 workflow · details", open=False):
|
| 3264 |
+
gr.Markdown(
|
| 3265 |
+
_section_info_markdown(
|
| 3266 |
+
"YOLO13 workflow",
|
| 3267 |
+
[
|
| 3268 |
+
("Detect Ball", "btn-detect-ball"),
|
| 3269 |
+
("Track Ball", "btn-track-ball-yolo"),
|
| 3270 |
+
("Detect Player", "btn-detect-player"),
|
| 3271 |
+
],
|
| 3272 |
+
)
|
| 3273 |
+
)
|
| 3274 |
with gr.Column(elem_classes=["model-section"]):
|
| 3275 |
with gr.Row(elem_classes=["model-row"]):
|
| 3276 |
gr.Markdown("SAM2", elem_classes=["model-label"])
|
|
|
|
| 3278 |
propagate_btn = gr.Button(
|
| 3279 |
"Track Ball", variant="stop", interactive=False, elem_id="btn-track-ball-sam"
|
| 3280 |
)
|
|
|
|
| 3281 |
propagate_player_btn = gr.Button(
|
| 3282 |
"Track Player",
|
| 3283 |
variant="stop",
|
| 3284 |
interactive=False,
|
| 3285 |
elem_id="btn-track-player-sam",
|
| 3286 |
)
|
|
|
|
| 3287 |
with gr.Row(elem_classes=["model-status"]):
|
| 3288 |
sam_kick_btn = gr.Button("⚽: N/A", interactive=False)
|
| 3289 |
sam_impact_btn = gr.Button("🚩: N/A", interactive=False)
|
| 3290 |
kick_plot = gr.Plot(label="Kick & impact diagnostics", show_label=True)
|
| 3291 |
+
with gr.Accordion("SAM2 tracking · details", open=False):
|
| 3292 |
+
gr.Markdown(
|
| 3293 |
+
_section_info_markdown(
|
| 3294 |
+
"SAM2 tracking",
|
| 3295 |
+
[
|
| 3296 |
+
("Track Ball", "btn-track-ball-sam"),
|
| 3297 |
+
("Track Player", "btn-track-player-sam"),
|
| 3298 |
+
],
|
| 3299 |
+
)
|
| 3300 |
+
)
|
| 3301 |
with gr.Row():
|
| 3302 |
min_impact_speed_slider = gr.Slider(
|
| 3303 |
label="Min impact speed (km/h)",
|
|
|
|
| 3368 |
show_progress=True,
|
| 3369 |
)
|
| 3370 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3371 |
example_video_path = ensure_example_video()
|
| 3372 |
examples_list = [
|
| 3373 |
[None, example_video_path],
|