Skip to content

feat/shared foundation for analytics (PR 1) - #59

Open
AlexBodner wants to merge 2 commits into
mainfrom
feat/soccer-pr1-foundation
Open

feat/shared foundation for analytics (PR 1)#59
AlexBodner wants to merge 2 commits into
mainfrom
feat/soccer-pr1-foundation

Conversation

@AlexBodner

@AlexBodner AlexBodner commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Add class/team ids, text drawing helper, team lock helpers, and core pitch homography fitting. Example main.py imports shared class ids; legacy modes unchanged.

Stack

These soccer analytics PRs merge in order: #59#60#61#63#64#65#66#67. Each later PR targets the previous branch.

Demo

No new example mode in this PR (shared library foundation only). Mode demo renders start in PR 2 — DIRECTION. All preview clips: soccer-analytics-pr-demos release.

Type of Change

  • New feature (non-breaking change that adds functionality)

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Add class/team ids, text drawing helper, team lock helpers, and core
pitch homography fitting. Example main.py imports shared class ids;
legacy modes unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR lays shared “foundation” utilities for the soccer analytics pipeline by centralizing detection/team IDs, adding reusable drawing and team-lock helpers, and introducing a core pitch homography fitting module.

Changes:

  • Centralize soccer detection class IDs and team IDs in sports/configs/soccer.py, and update the soccer example to import them.
  • Add clip-level team “lock” helpers in sports/common/team.py to stabilize team assignments by tracker ID majority vote.
  • Introduce sports/common/homography.py (RANSAC-capable homography fit + helpers) and sports/common/draw.py (text-with-shadow helper).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
sports/configs/soccer.py Adds shared detection class IDs and team ID constants.
sports/common/team.py Adds team-lock utilities keyed by tracker IDs.
sports/common/homography.py Adds pitch keypoint normalization + homography fitting utilities.
sports/common/draw.py Adds a reusable OpenCV text shadow drawing helper.
examples/soccer/main.py Switches example constants to import shared IDs from config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sports/common/team.py
Comment on lines +117 to +134
def lock_teams_by_tracklet_majority(
frames: list[tuple[int, sv.Detections]],
) -> dict[int, int]:
"""Lock one team per tracker id using a majority shirt-colour vote.

Args:
frames: Sequence of ``(frame_idx, detections)`` pairs from a clip.

Returns:
Mapping of ``tracker_id`` to locked team id (0 or 1).
"""
votes: dict[int, list[int]] = {}
for _, dets in frames:
if dets.tracker_id is None or dets.data is None:
continue
team = np.asarray(
dets.data.get("team", np.full(len(dets), TEAM_NONE)), dtype=int
)
Comment thread sports/common/team.py
Comment on lines +143 to +147
locked: dict[int, int] = {}
for tid, vals in votes.items():
counts = np.bincount(np.asarray(vals, dtype=int), minlength=2)
locked[tid] = int(np.argmax(counts))
return locked
Comment thread sports/common/team.py
Comment on lines +150 to +154
def apply_team_lock(
team_arr: np.ndarray,
tracker_id: np.ndarray | None,
team_lock: dict[int, int],
) -> np.ndarray:
Comment thread sports/common/team.py
Comment on lines +174 to +176
def relock_detection_teams(
dets: sv.Detections, team_lock: dict[int, int]
) -> sv.Detections:
Comment on lines +39 to +52
if use_ransac and len(src) >= 4:
m_inv, _ = cv2.findHomography(
dst, src, cv2.RANSAC, ransacReprojThreshold=ransac_thresh
)
if m_inv is not None:
try:
self.m = np.linalg.inv(m_inv)
return
except np.linalg.LinAlgError:
pass
m, _ = cv2.findHomography(src, dst)
if m is None:
raise ValueError("Homography matrix could not be calculated.")
self.m = m
Addresses Copilot review on typing vs python_requires and RANSAC fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AlexBodner

Copy link
Copy Markdown
Collaborator Author

Addressed Copilot notes: python_requires>=3.9, from __future__ import annotations in team.py, and forward RANSAC in RansacViewTransformer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants