Spaces:
Sleeping
Sleeping
# utils.py | |
import cv2 | |
import numpy as np | |
def analyze_frame_sequence(frames): | |
# Simulate realistic trajectory with umpire's call zone | |
h, w = frames[0].shape[:2] | |
trajectory_curve = [(200 + i * 10, 300 - i * 8 + i ** 2 // 5) for i in range(6)] # parabolic downward curve | |
last = trajectory_curve[-1] | |
return { | |
"pitch": "in line", | |
"impact": "in line", | |
"trajectory": "umpires call", # simulate marginal zone | |
"shot_offered": True, | |
"pitch_point": trajectory_curve[0], | |
"impact_point": trajectory_curve[2], | |
"trajectory_curve": trajectory_curve, | |
"stump_zone": [(last[0] - 20, last[1] - 40), (last[0] + 20, last[1] + 40)], | |
"hit_point": last | |
} | |
def make_decision(analysis): | |
if analysis['pitch'] == 'outside leg': | |
return "NOT OUT", "Pitched outside leg stump." | |
if analysis['impact'] == 'outside off' and analysis['shot_offered']: | |
return "NOT OUT", "Impact outside off with shot offered." | |
if analysis['trajectory'] == 'missing': | |
return "NOT OUT", "Ball missing stumps." | |
if analysis['trajectory'] == 'umpires call': | |
return "UMPIRE’S CALL", "Marginal trajectory impact." | |
return "OUT", "Impact in line and ball hitting stumps." | |
def overlay_text(frame, text, pos, size=1.0, color=(255,255,255)): | |
cv2.putText(frame, text, pos, cv2.FONT_HERSHEY_SIMPLEX, size, color, 2, cv2.LINE_AA) | |
def overlay_annotations_dynamic(frame, analysis, t): | |
h, w = frame.shape[:2] | |
if t < len(analysis['trajectory_curve']): | |
ball_pos = analysis['trajectory_curve'][t] | |
cv2.circle(frame, ball_pos, 8, (0, 255, 0) if analysis['trajectory'] == 'hitting' else (0, 140, 255), -1) | |
# Draw dashed arc | |
for i in range(1, min(t + 1, len(analysis['trajectory_curve']))): | |
p1 = analysis['trajectory_curve'][i - 1] | |
p2 = analysis['trajectory_curve'][i] | |
if i % 2 == 0: | |
cv2.line(frame, p1, p2, (0, 255, 0) if analysis['trajectory'] == 'hitting' else (0, 140, 255), 2) | |
# Umpire's Call Zone | |
x1, y1 = analysis['stump_zone'][0] | |
x2, y2 = analysis['stump_zone'][1] | |
overlay = frame.copy() | |
color = (0, 255, 0) if analysis['trajectory'] == 'hitting' else (0, 140, 255) # amber for umpire's call | |
cv2.rectangle(overlay, (x1, y1), (x2, y2), color, -1) | |
frame[:] = cv2.addWeighted(overlay, 0.25, frame, 0.75, 0) | |
cv2.rectangle(frame, (x1, y1), (x2, y2), (180, 180, 180), 2) | |
shot_icon = "✓" if analysis['shot_offered'] else "✗" | |
overlay_text(frame, f"Shot Offered: {shot_icon}", (30, 170), 0.8, (0,255,0) if analysis['shot_offered'] else (0,0,255)) | |
overlay_text(frame, f"Trajectory: {analysis['trajectory'].capitalize()}", (30, 130), 0.8, | |
(0, 255, 0) if analysis['trajectory'] == 'hitting' else (0, 140, 255)) | |
overlay_text(frame, "ICC LBW Review • Third-Umpire Analysis", (30, h - 30), 0.6, (180,180,180)) | |
def render_annotated_clip(frames, analysis, decision, reason, output_path): | |
h, w = frames[0].shape[:2] | |
out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'mp4v'), 20.0, (w, h)) | |
# Verdict card (1s) | |
verdict_card = np.zeros_like(frames[0]) | |
overlay_text(verdict_card, f"FINAL DECISION: {decision}", (50, 200), 2.0, (255,255,255)) | |
overlay_text(verdict_card, reason, (50, 250), 0.9, (200,200,200)) | |
for _ in range(20): | |
out.write(verdict_card) | |
# Dynamic replay with animated ball tracking and umpire's call zone | |
for t, f in enumerate(frames): | |
annotated = f.copy() | |
overlay_annotations_dynamic(annotated, analysis, t) | |
out.write(annotated) | |
# Slow-motion: emphasize final 3 points | |
for pt in analysis['trajectory_curve'][-3:]: | |
for _ in range(2): | |
f = frames[-1].copy() | |
cv2.circle(f, pt, 10, (0,255,0) if analysis['trajectory']=="hitting" else (0,140,255), -1) | |
cv2.rectangle(f, analysis['stump_zone'][0], analysis['stump_zone'][1], (180,180,180), 2) | |
overlay_text(f, "Trajectory End", (pt[0] + 10, pt[1] - 10), 0.8, (255,255,255)) | |
overlay_text(f, "ICC LBW Review • Third-Umpire Analysis", (30, h - 30), 0.6, (180,180,180)) | |
out.write(f) | |
for _ in range(10): | |
out.write(verdict_card) | |
out.release() | |