Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -34,6 +34,11 @@ 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
|
@@ -55,61 +60,31 @@ from drs_modules.visualization import (
|
|
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 |
-
|
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,
|
@@ -124,7 +99,6 @@ def analyse_appeal(video_path: str, review_seconds: int = 8) -> Tuple[str, Dict[
|
|
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,
|
@@ -138,48 +112,37 @@ def analyse_appeal(video_path: str, review_seconds: int = 8) -> Tuple[str, Dict[
|
|
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 |
-
|
147 |
-
|
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 |
-
file_path = video_file.name if hasattr(video_file, "name")else video_file
|
170 |
-
#save_path = save_uploaded_video(video_file, video_file)
|
171 |
return save_uploaded_video(file_path, video_file)
|
172 |
|
173 |
-
video_input
|
174 |
-
|
175 |
-
|
|
|
176 |
)
|
177 |
|
178 |
gr.Markdown(
|
179 |
"""
|
180 |
-
|
181 |
-
|
182 |
-
"""
|
183 |
)
|
184 |
|
185 |
with gr.Tab("LBW Review"):
|
@@ -188,19 +151,12 @@ def build_interface() -> gr.Blocks:
|
|
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 |
-
|
194 |
-
)
|
195 |
-
|
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
|
@@ -239,3 +195,4 @@ def build_interface() -> gr.Blocks:
|
|
239 |
if __name__ == "__main__":
|
240 |
demo = build_interface()
|
241 |
demo.launch()
|
|
|
|
34 |
fully self contained and uses only packages available in this runtime.
|
35 |
"""
|
36 |
|
37 |
+
"""
|
38 |
+
Digital Review System (DRS) application for LBW decisions
|
39 |
+
========================================================
|
40 |
+
"""
|
41 |
+
|
42 |
from __future__ import annotations
|
43 |
|
44 |
import os
|
|
|
60 |
|
61 |
|
62 |
def analyse_appeal(video_path: str, review_seconds: int = 8) -> Tuple[str, Dict[str, Any]]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
temp_dir = tempfile.mkdtemp()
|
64 |
trimmed_path = os.path.join(temp_dir, "trimmed.mp4")
|
65 |
|
|
|
66 |
trim_last_seconds(video_path, trimmed_path, review_seconds)
|
|
|
|
|
67 |
tracking_data = detect_and_track_ball(trimmed_path)
|
|
|
|
|
|
|
68 |
trajectory_model = estimate_trajectory(tracking_data["centers"], tracking_data["timestamps"])
|
69 |
will_hit_stumps = predict_stumps_intersection(trajectory_model)
|
|
|
|
|
70 |
decision, impact_frame_idx = make_lbw_decision(
|
71 |
+
tracking_data["centers"], trajectory_model, will_hit_stumps
|
|
|
|
|
72 |
)
|
73 |
|
|
|
74 |
total_distance_px = 0.0
|
75 |
for i in range(1, len(tracking_data["centers"])):
|
76 |
cx0, cy0 = tracking_data["centers"][i - 1]
|
77 |
cx1, cy1 = tracking_data["centers"][i]
|
78 |
total_distance_px += ((cx1 - cx0) ** 2 + (cy1 - cy0) ** 2) ** 0.5
|
79 |
+
|
80 |
duration = tracking_data["timestamps"][-1] - tracking_data["timestamps"][0]
|
81 |
if duration <= 0:
|
82 |
speed_kmh = 0.0
|
83 |
else:
|
|
|
84 |
pixels_per_metre = 50.0
|
85 |
speed_mps = (total_distance_px / pixels_per_metre) / duration
|
86 |
speed_kmh = speed_mps * 3.6
|
87 |
|
|
|
88 |
annotated_video_path = os.path.join(temp_dir, "annotated.mp4")
|
89 |
annotate_video_with_tracking(
|
90 |
trimmed_path,
|
|
|
99 |
tracking_data["centers"], trajectory_model, will_hit_stumps, plot_path
|
100 |
)
|
101 |
|
|
|
102 |
decision_message = f"Decision: {decision}"
|
103 |
result = {
|
104 |
"decision": decision,
|
|
|
112 |
|
113 |
|
114 |
def build_interface() -> gr.Blocks:
|
|
|
115 |
with gr.Blocks(title="Cricket LBW DRS Demo") as demo:
|
116 |
gr.Markdown(
|
117 |
"""# Digital Review System (LBW)
|
118 |
+
This demo lets you record or upload cricket match footage and analyse LBW appeals.
|
119 |
+
You'll get a 3D trajectory plot, annotated replay, and OUT/NOT OUT decision.
|
120 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
)
|
122 |
|
123 |
with gr.Tab("Live Match Recording"):
|
124 |
video_input = gr.Video(
|
125 |
label="Record or upload match video",
|
126 |
+
sources=["upload", "webcam"]
|
|
|
|
|
|
|
127 |
)
|
128 |
out_video_path = gr.State()
|
129 |
|
130 |
def on_video_upload(video_file):
|
131 |
if video_file is None:
|
132 |
return None
|
133 |
+
file_path = video_file.name if hasattr(video_file, "name") else video_file
|
|
|
134 |
return save_uploaded_video(file_path, video_file)
|
135 |
|
136 |
+
video_input.change(
|
137 |
+
fn=on_video_upload,
|
138 |
+
inputs=[video_input],
|
139 |
+
outputs=[out_video_path],
|
140 |
)
|
141 |
|
142 |
gr.Markdown(
|
143 |
"""
|
144 |
+
After recording or uploading a video, switch to the **LBW Review** tab and click **Analyse Appeal**.
|
145 |
+
"""
|
|
|
146 |
)
|
147 |
|
148 |
with gr.Tab("LBW Review"):
|
|
|
151 |
review_seconds = gr.Number(
|
152 |
value=8, label="Seconds to review", minimum=2, maximum=20
|
153 |
)
|
154 |
+
|
155 |
decision_output = gr.Textbox(label="Decision", lines=1)
|
156 |
+
ball_speed_output = gr.Textbox(label="Ball speed (km/h)", lines=1, interactive=False)
|
157 |
+
impact_frame_output = gr.Textbox(label="Impact frame index", lines=1, interactive=False)
|
158 |
+
annotated_video_output = gr.Video(label="Annotated replay video")
|
159 |
+
trajectory_plot_output = gr.Image(label="3D Trajectory plot")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
|
161 |
def on_analyse(_):
|
162 |
video_path = out_video_path.value
|
|
|
195 |
if __name__ == "__main__":
|
196 |
demo = build_interface()
|
197 |
demo.launch()
|
198 |
+
|