# 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()