dschandra commited on
Commit
efa6a69
·
verified ·
1 Parent(s): 9ffd92a

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +241 -0
  2. requirements.txt +6 -0
app.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Digital Review System (DRS) application for LBW decisions
3
+ ========================================================
4
+
5
+ This application provides a simplified demonstration of how a cricket‑style
6
+ digital review system (DRS) could be implemented using open source
7
+ computer vision tools. It is not a complete Hawk‑Eye replacement, but
8
+ illustrates the key steps in building such a system: capturing video,
9
+ detecting and tracking the ball, estimating its flight trajectory,
10
+ analysing whether it would have hit the stumps, estimating speed and
11
+ generating a replay with annotations. A Gradio interface ties these
12
+ components together to provide an easy way to record a match, appeal
13
+ for an LBW decision and review the result.
14
+
15
+ The app has two main pages:
16
+
17
+ • **Live Match Recording** – allows the user to upload or record match video.
18
+ The video is stored on disk and can be analysed later.
19
+
20
+ • **LBW Review** – analyses the last few seconds of the recorded video
21
+ whenever an appeal is made. It performs ball tracking, trajectory
22
+ estimation and stumps intersection checks to predict whether the
23
+ batsman is out or not. An annotated replay and a 3D trajectory
24
+ visualisation are returned along with speed and impact information.
25
+
26
+ The implementation relies on simple background subtraction and circle
27
+ detection rather than proprietary tracking systems. It assumes a
28
+ single static camera behind the bowler and a fairly unobstructed view
29
+ of the pitch. See the individual modules in the ``modules`` package
30
+ for more details on each processing step.
31
+
32
+ Note: because this space is intended to run on Hugging Face, file
33
+ paths and heavy downloads are avoided wherever possible. The code is
34
+ fully self contained and uses only packages available in this runtime.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import os
40
+ import shutil
41
+ import tempfile
42
+ from pathlib import Path
43
+ from typing import Any, Dict, Tuple
44
+
45
+ import gradio as gr
46
+
47
+ from drs_modules.modules.video_processing import trim_last_seconds, save_uploaded_video
48
+ from drs_modules.modules.detection import detect_and_track_ball
49
+ from drs_modules.modules.trajectory import estimate_trajectory, predict_stumps_intersection
50
+ from drs_modules.modules.lbw_decision import make_lbw_decision
51
+ from drs_modules.modules.visualization import (
52
+ generate_trajectory_plot,
53
+ annotate_video_with_tracking,
54
+ )
55
+
56
+
57
+ def analyse_appeal(video_path: str, review_seconds: int = 8) -> Tuple[str, Dict[str, Any]]:
58
+ """Analyse the last few seconds of a match video and return DRS results.
59
+
60
+ Parameters
61
+ ----------
62
+ video_path: str
63
+ Path to the full match video recorded on the Live Match Recording page.
64
+ review_seconds: int, optional
65
+ Number of seconds from the end of the video to analyse. Defaults to 8.
66
+
67
+ Returns
68
+ -------
69
+ Tuple[str, Dict[str, Any]]
70
+ A message summarising the decision and a dictionary with the
71
+ underlying data for display (decision text, ball speed, impact
72
+ frame number, annotated video path and trajectory plot path).
73
+ """
74
+ # Create a temporary directory to hold intermediate files
75
+ temp_dir = tempfile.mkdtemp()
76
+ trimmed_path = os.path.join(temp_dir, "trimmed.mp4")
77
+
78
+ # Step 1: Trim the last N seconds of the input video
79
+ trim_last_seconds(video_path, trimmed_path, review_seconds)
80
+
81
+ # Step 2: Detect and track the ball through the trimmed segment
82
+ tracking_data = detect_and_track_ball(trimmed_path)
83
+
84
+ # Step 3: Estimate the ball's trajectory (2D for simplicity) and predict
85
+ # whether it will hit the stumps
86
+ trajectory_model = estimate_trajectory(tracking_data["centers"], tracking_data["timestamps"])
87
+ will_hit_stumps = predict_stumps_intersection(trajectory_model)
88
+
89
+ # Step 4: Make a decision based on trajectory and impact detection
90
+ decision, impact_frame_idx = make_lbw_decision(
91
+ tracking_data["centers"],
92
+ trajectory_model,
93
+ will_hit_stumps,
94
+ )
95
+
96
+ # Step 5: Calculate ball speed (pixels per second scaled to km/h)
97
+ total_distance_px = 0.0
98
+ for i in range(1, len(tracking_data["centers"])):
99
+ cx0, cy0 = tracking_data["centers"][i - 1]
100
+ cx1, cy1 = tracking_data["centers"][i]
101
+ total_distance_px += ((cx1 - cx0) ** 2 + (cy1 - cy0) ** 2) ** 0.5
102
+ # Duration of captured frames
103
+ duration = tracking_data["timestamps"][-1] - tracking_data["timestamps"][0]
104
+ if duration <= 0:
105
+ speed_kmh = 0.0
106
+ else:
107
+ # Convert pixel distance per second to km/h using an assumed scale
108
+ pixels_per_metre = 50.0
109
+ speed_mps = (total_distance_px / pixels_per_metre) / duration
110
+ speed_kmh = speed_mps * 3.6
111
+
112
+ # Step 6: Generate annotated replay video and trajectory plot
113
+ annotated_video_path = os.path.join(temp_dir, "annotated.mp4")
114
+ annotate_video_with_tracking(
115
+ trimmed_path,
116
+ tracking_data["centers"],
117
+ trajectory_model,
118
+ will_hit_stumps,
119
+ impact_frame_idx,
120
+ annotated_video_path,
121
+ )
122
+ plot_path = os.path.join(temp_dir, "trajectory_plot.png")
123
+ generate_trajectory_plot(
124
+ tracking_data["centers"], trajectory_model, will_hit_stumps, plot_path
125
+ )
126
+
127
+ # Compose the message and result dictionary
128
+ decision_message = f"Decision: {decision}"
129
+ result = {
130
+ "decision": decision,
131
+ "ball_speed_kmh": round(speed_kmh, 2),
132
+ "impact_frame_index": impact_frame_idx,
133
+ "annotated_video": annotated_video_path,
134
+ "trajectory_plot": plot_path,
135
+ }
136
+
137
+ return decision_message, result
138
+
139
+
140
+ def build_interface() -> gr.Blocks:
141
+ """Construct the Gradio interface with multiple pages."""
142
+ with gr.Blocks(title="Cricket LBW DRS Demo") as demo:
143
+ gr.Markdown(
144
+ """# Digital Review System (LBW)
145
+
146
+ This demo illustrates how a simplified digital review system can be
147
+ implemented using computer vision techniques. You can record or
148
+ upload match footage, and when an appeal occurs, the system will
149
+ analyse the last few seconds to decide whether the batsman is **OUT**
150
+ or **NOT OUT**. Alongside the decision you will receive an
151
+ annotated replay, a 3D trajectory plot and an estimate of the ball
152
+ speed.
153
+ """
154
+ )
155
+
156
+ with gr.Tab("Live Match Recording"):
157
+ video_input = gr.Video(
158
+ label="Record or upload match video",
159
+ sources=["upload", "webcam"],
160
+ # Do not specify `type` because some versions of Gradio
161
+ # reject that argument. The file path is available via
162
+ # video_file.name in the callback.
163
+ )
164
+ out_video_path = gr.State()
165
+
166
+ def on_video_upload(video_file):
167
+ if video_file is None:
168
+ return None
169
+ save_path = save_uploaded_video(video_file.name, video_file)
170
+ return save_path
171
+
172
+ video_input.change(
173
+ fn=on_video_upload,
174
+ inputs=[video_input],
175
+ outputs=[out_video_path],
176
+ )
177
+
178
+ gr.Markdown(
179
+ """
180
+ After recording or uploading a video, switch to the **LBW Review**
181
+ tab and press **Analyse Appeal** to review the last 8 seconds.
182
+ """
183
+ )
184
+
185
+ with gr.Tab("LBW Review"):
186
+ with gr.Row():
187
+ analyse_button = gr.Button("Analyse Appeal")
188
+ review_seconds = gr.Number(
189
+ value=8, label="Seconds to review", minimum=2, maximum=20
190
+ )
191
+ decision_output = gr.Textbox(label="Decision", lines=1)
192
+ ball_speed_output = gr.Textbox(
193
+ label="Ball speed (km/h)", lines=1, interactive=False
194
+ )
195
+ impact_frame_output = gr.Textbox(
196
+ label="Impact frame index", lines=1, interactive=False
197
+ )
198
+ annotated_video_output = gr.Video(
199
+ label="Annotated replay video"
200
+ )
201
+ trajectory_plot_output = gr.Image(
202
+ label="3D Trajectory plot"
203
+ )
204
+
205
+ def on_analyse(_):
206
+ video_path = out_video_path.value
207
+ if not video_path or not os.path.exists(video_path):
208
+ return (
209
+ "Please record or upload a video in the first tab.",
210
+ None,
211
+ None,
212
+ None,
213
+ None,
214
+ )
215
+ message, result = analyse_appeal(video_path, int(review_seconds.value))
216
+ return (
217
+ message,
218
+ str(result["ball_speed_kmh"]),
219
+ str(result["impact_frame_index"]),
220
+ result["annotated_video"],
221
+ result["trajectory_plot"],
222
+ )
223
+
224
+ analyse_button.click(
225
+ fn=on_analyse,
226
+ inputs=[analyse_button],
227
+ outputs=[
228
+ decision_output,
229
+ ball_speed_output,
230
+ impact_frame_output,
231
+ annotated_video_output,
232
+ trajectory_plot_output,
233
+ ],
234
+ )
235
+
236
+ return demo
237
+
238
+
239
+ if __name__ == "__main__":
240
+ demo = build_interface()
241
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+
2
+ opencv-python
3
+ numpy
4
+ matplotlib
5
+ gradio
6
+ pillow