Skip to content

Commit a591208

Browse files
Add minimap goal shading from locked defending teams (PR6).
draw_goals_on_pitch on trace and radar minimaps via locks.locked_goal_defenders; no GK logic changes (lives on PR3). Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e6e8ad4 commit a591208

5 files changed

Lines changed: 66 additions & 2 deletions

File tree

examples/soccer/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ on the field.
145145
--device mps --mode DISTANCE --max-frames 90
146146
```
147147

148-
Requires pitch keypoints (same flags as SPEED).
148+
Requires pitch keypoints (same flags as SPEED). Minimap shows defending-team
149+
goal shading when homography locks are available.
149150

150151
- `SPEED_AND_DISTANCE` — Speed + distance chips and per-player trace lines on the
151152
radar minimap (no end-card). Default shows all players; pass `--track-id N` to

examples/soccer/distance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def _render_speed_distance_traces(
148148
draw_distance_labels(annotated, marked, dist_by_tid)
149149
mini_radar = build_trace_minimap(
150150
dets, radar_transformer, trace_by_tid, focus_tid,
151+
locked_goal_defenders=locks.locked_goal_defenders,
151152
)
152153
overlay_minimap(annotated, mini_radar)
153154
else:
@@ -160,6 +161,7 @@ def _render_speed_distance_traces(
160161
draw_speed_legend(annotated)
161162
mini_radar = build_trace_minimap(
162163
dets, radar_transformer, trace_by_tid, None,
164+
locked_goal_defenders=locks.locked_goal_defenders,
163165
)
164166
overlay_minimap(annotated, mini_radar)
165167
sink.write_frame(annotated)

examples/soccer/speed.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def _render_speed(args, session: VideoTrackingSession) -> None:
161161
)
162162
draw_radar_minimap(
163163
annotated, dets, minimap_transforms.get(frame_idx),
164+
locked_goal_defenders=locks.locked_goal_defenders,
164165
)
165166
sink.write_frame(annotated)
166167
finally:

sports/annotators/motion.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import numpy as np
55
import supervision as sv
66

7-
from sports.annotators.soccer import draw_pitch, draw_points_on_pitch, player_ellipse_annotator
7+
from sports.annotators.soccer import draw_pitch, draw_points_on_pitch, draw_goals_on_pitch, player_ellipse_annotator
88
from sports.common.draw import draw_text_shadow
99
from sports.common.homography import valid_pitch_cm
1010
from sports.common.kinematics import (
@@ -305,12 +305,24 @@ def draw_radar_minimap(
305305
margin_x: int = 12,
306306
margin_y: int = 12,
307307
alpha: float = RADAR_MINIMAP_ALPHA,
308+
locked_goal_defenders: tuple[int, int] | None = None,
308309
) -> np.ndarray:
309310
"""Overlay a plain translucent radar minimap in the bottom-right corner."""
310311
if transformer is None:
311312
return frame
312313
config = SoccerPitchConfiguration()
313314
radar = draw_pitch(config=config, padding=padding, scale=minimap_scale)
315+
if locked_goal_defenders is not None:
316+
left_def, right_def = locked_goal_defenders
317+
if left_def in (0, 1) and right_def in (0, 1):
318+
radar = draw_goals_on_pitch(
319+
config,
320+
left_defender_team=left_def,
321+
right_defender_team=right_def,
322+
padding=padding,
323+
scale=minimap_scale,
324+
pitch=radar,
325+
)
314326
pmask = player_mask(detections)
315327
if pmask.any():
316328
pdet = detections[pmask]
@@ -422,6 +434,7 @@ def build_trace_minimap(
422434
config=None,
423435
scale: float = RADAR_MINIMAP_SCALE,
424436
padding: int = RADAR_MINIMAP_PAD,
437+
locked_goal_defenders: tuple[int, int] | None = None,
425438
) -> np.ndarray:
426439
"""Build a radar minimap with per-track colored traces and current player dots."""
427440
if focus_tid is not None:
@@ -430,6 +443,17 @@ def build_trace_minimap(
430443
if config is None:
431444
config = SoccerPitchConfiguration()
432445
radar = draw_pitch(config=config, padding=padding, scale=scale)
446+
if locked_goal_defenders is not None:
447+
left_def, right_def = locked_goal_defenders
448+
if left_def in (0, 1) and right_def in (0, 1):
449+
radar = draw_goals_on_pitch(
450+
config,
451+
left_defender_team=left_def,
452+
right_defender_team=right_def,
453+
padding=padding,
454+
scale=scale,
455+
pitch=radar,
456+
)
433457

434458
if focus_tid is None:
435459
trace_items = trace_by_tid.items()

sports/annotators/soccer.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,42 @@ def draw_pitch(
107107
return pitch_image
108108

109109

110+
def draw_goals_on_pitch(
111+
config: SoccerPitchConfiguration,
112+
*,
113+
left_defender_team: int,
114+
right_defender_team: int,
115+
pitch: np.ndarray,
116+
padding: int = 50,
117+
scale: float = 0.1,
118+
fill_alpha: float = 0.38,
119+
) -> np.ndarray:
120+
"""Highlight each goal mouth in the defending team's color."""
121+
team_colors = [sv.Color.from_hex(c) for c in PLAYER_VIS_COLORS[:2]]
122+
w = config.width
123+
length = config.length
124+
gbw = config.goal_box_width
125+
gbl = config.goal_box_length
126+
y0, y1 = (w - gbw) / 2, (w + gbw) / 2
127+
128+
def _goal_patch(goal_x_cm: float, defender: int, depth_cm: float) -> None:
129+
color = team_colors[defender % len(team_colors)].as_bgr()
130+
mouth_x = int(goal_x_cm * scale) + padding
131+
py0 = int(y0 * scale) + padding
132+
py1 = int(y1 * scale) + padding
133+
inner_x = int((goal_x_cm + depth_cm) * scale) + padding
134+
x_lo, x_hi = sorted((mouth_x, inner_x))
135+
overlay = pitch.copy()
136+
cv2.rectangle(overlay, (x_lo, py0), (x_hi, py1), color, -1)
137+
cv2.addWeighted(overlay, fill_alpha, pitch, 1.0 - fill_alpha, 0, pitch)
138+
cv2.line(pitch, (mouth_x, py0), (mouth_x, py1), color, 5, cv2.LINE_AA)
139+
cv2.line(pitch, (mouth_x, py0), (mouth_x, py1), (255, 255, 255), 1, cv2.LINE_AA)
140+
141+
_goal_patch(0.0, left_defender_team, gbl)
142+
_goal_patch(float(length), right_defender_team, -gbl)
143+
return pitch
144+
145+
110146
def draw_points_on_pitch(
111147
config: SoccerPitchConfiguration,
112148
xy: np.ndarray,

0 commit comments

Comments
 (0)