Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,11 +5,12 @@ import time
|
|
5 |
import json
|
6 |
import random
|
7 |
import logging
|
8 |
-
import numpy as np
|
9 |
from datetime import datetime
|
10 |
from collections import Counter
|
11 |
from typing import Any, Dict, List, Optional, Tuple
|
12 |
-
import matplotlib.pyplot as plt
|
|
|
13 |
|
14 |
# Suppress Ultralytics warning by setting a writable config directory
|
15 |
os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
|
@@ -53,15 +54,16 @@ logging.basicConfig(
|
|
53 |
|
54 |
# Global variables
|
55 |
paused: bool = False
|
56 |
-
frame_rate: float = 0.
|
57 |
frame_count: int = 0
|
58 |
log_entries: List[str] = []
|
59 |
-
crack_counts: List[int] = []
|
60 |
-
crack_severity_all: List[str] = []
|
61 |
last_frame: Optional[np.ndarray] = None
|
62 |
last_detections: Dict[str, Any] = {}
|
63 |
last_timestamp: str = ""
|
64 |
last_detected_images: List[str] = []
|
|
|
65 |
gps_coordinates: List[List[float]] = []
|
66 |
video_loaded: bool = False
|
67 |
active_service: Optional[str] = None
|
@@ -75,15 +77,6 @@ os.makedirs(CAPTURED_FRAMES_DIR, exist_ok=True)
|
|
75 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
76 |
|
77 |
def initialize_video(video_file: Optional[Any] = None) -> str:
|
78 |
-
"""
|
79 |
-
Initialize the video file for processing.
|
80 |
-
|
81 |
-
Args:
|
82 |
-
video_file (Optional[Any]): Uploaded video file object (from Gradio).
|
83 |
-
|
84 |
-
Returns:
|
85 |
-
str: Status message indicating success or failure.
|
86 |
-
"""
|
87 |
global video_loaded, log_entries
|
88 |
release_video()
|
89 |
video_path = DEFAULT_VIDEO_PATH
|
@@ -106,19 +99,6 @@ def set_active_service(
|
|
106 |
rs_val: bool,
|
107 |
pl_val: bool
|
108 |
) -> Tuple[Optional[str], str]:
|
109 |
-
"""
|
110 |
-
Set the active service category based on user selection.
|
111 |
-
|
112 |
-
Args:
|
113 |
-
service_name (str): Name of the service being toggled.
|
114 |
-
uc_val (bool): Under Construction toggle value.
|
115 |
-
om_val (bool): Operations Maintenance toggle value.
|
116 |
-
rs_val (bool): Road Safety toggle value.
|
117 |
-
pl_val (bool): Plantation toggle value.
|
118 |
-
|
119 |
-
Returns:
|
120 |
-
Tuple[Optional[str], str]: Active service name and status message.
|
121 |
-
"""
|
122 |
global active_service
|
123 |
toggles = {
|
124 |
"under_construction": uc_val,
|
@@ -146,36 +126,27 @@ def set_active_service(
|
|
146 |
return None, "No Service Category Enabled"
|
147 |
|
148 |
def generate_crack_trend_chart() -> Optional[plt.Figure]:
|
149 |
-
"""
|
150 |
-
Generate a line chart for crack trend over time using Matplotlib.
|
151 |
-
|
152 |
-
Returns:
|
153 |
-
Optional[plt.Figure]: Matplotlib figure object or None if no data.
|
154 |
-
"""
|
155 |
if not crack_counts:
|
156 |
return None
|
157 |
|
158 |
data = crack_counts[-50:]
|
159 |
labels = list(range(len(data)))
|
160 |
|
161 |
-
fig, ax = plt.subplots(figsize=(6, 4))
|
162 |
-
ax.plot(labels, data, color="#FF6347", label="Cracks Over Time")
|
163 |
-
ax.set_title("Crack Trend (Operations Maintenance)")
|
164 |
-
ax.set_xlabel("Frame")
|
165 |
-
ax.set_ylabel("Count")
|
166 |
ax.set_ylim(bottom=0)
|
167 |
-
ax.grid(True)
|
168 |
-
ax.
|
|
|
|
|
|
|
169 |
plt.tight_layout()
|
170 |
return fig
|
171 |
|
172 |
def generate_crack_severity_chart() -> Optional[plt.Figure]:
|
173 |
-
"""
|
174 |
-
Generate a pie chart for crack severity distribution using Matplotlib.
|
175 |
-
|
176 |
-
Returns:
|
177 |
-
Optional[plt.Figure]: Matplotlib figure object or None if no data.
|
178 |
-
"""
|
179 |
if not crack_severity_all:
|
180 |
return None
|
181 |
|
@@ -183,25 +154,24 @@ def generate_crack_severity_chart() -> Optional[plt.Figure]:
|
|
183 |
labels = list(count.keys())
|
184 |
sizes = list(count.values())
|
185 |
|
186 |
-
fig, ax = plt.subplots(figsize=(6, 4))
|
187 |
-
|
|
|
188 |
sizes,
|
189 |
labels=labels,
|
190 |
-
colors=
|
191 |
-
autopct=
|
192 |
-
startangle=90
|
|
|
|
|
193 |
)
|
194 |
-
ax.set_title("Crack Severity (Operations Maintenance)")
|
|
|
|
|
195 |
plt.tight_layout()
|
196 |
return fig
|
197 |
|
198 |
def generate_severity_distribution_chart() -> Optional[plt.Figure]:
|
199 |
-
"""
|
200 |
-
Generate a bar chart for crack severity distribution using Matplotlib.
|
201 |
-
|
202 |
-
Returns:
|
203 |
-
Optional[plt.Figure]: Matplotlib figure object or None if no data.
|
204 |
-
"""
|
205 |
if not crack_severity_all:
|
206 |
return None
|
207 |
|
@@ -209,12 +179,19 @@ def generate_severity_distribution_chart() -> Optional[plt.Figure]:
|
|
209 |
labels = list(count.keys())
|
210 |
sizes = list(count.values())
|
211 |
|
212 |
-
fig, ax = plt.subplots(figsize=(6, 4))
|
213 |
-
|
214 |
-
ax.
|
215 |
-
ax.
|
216 |
-
ax.
|
|
|
217 |
ax.set_ylim(bottom=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
plt.tight_layout()
|
219 |
return fig
|
220 |
|
@@ -227,21 +204,8 @@ def monitor_feed() -> Tuple[
|
|
227 |
Optional[plt.Figure],
|
228 |
List[str]
|
229 |
]:
|
230 |
-
"""
|
231 |
-
Process video frames and perform detections based on the active service.
|
232 |
-
|
233 |
-
Returns:
|
234 |
-
Tuple containing:
|
235 |
-
- Processed frame (numpy array or None).
|
236 |
-
- Detection metrics (JSON string with crack trend and severity data).
|
237 |
-
- Recent logs (string).
|
238 |
-
- Crack trend chart (Matplotlib figure or None).
|
239 |
-
- Crack severity chart (Matplotlib figure or None).
|
240 |
-
- Severity distribution chart (Matplotlib figure or None).
|
241 |
-
- List of captured image paths.
|
242 |
-
"""
|
243 |
global paused, frame_count, last_frame, last_detections, last_timestamp
|
244 |
-
global gps_coordinates, last_detected_images, video_loaded
|
245 |
|
246 |
if not video_loaded:
|
247 |
log_entries.append("Cannot start streaming: Video not loaded successfully.")
|
@@ -317,7 +281,7 @@ def monitor_feed() -> Tuple[
|
|
317 |
frame = shadow_results["frame"]
|
318 |
all_detected_items.extend(shadow_dets)
|
319 |
|
320 |
-
#
|
321 |
if len(frame.shape) == 2:
|
322 |
thermal_results = process_thermal(frame)
|
323 |
thermal_dets = thermal_results["detections"]
|
@@ -329,9 +293,9 @@ def monitor_feed() -> Tuple[
|
|
329 |
logging.error(f"Processing error in {active_service}: {str(e)}")
|
330 |
all_detected_items = []
|
331 |
|
332 |
-
# Save temporary image for display
|
333 |
try:
|
334 |
-
cv2.imwrite(TEMP_IMAGE_PATH, frame, [int(cv2.IMWRITE_JPEG_QUALITY),
|
335 |
except Exception as e:
|
336 |
log_entries.append(f"Error saving temp image: {str(e)}")
|
337 |
logging.error(f"Error saving temp image: {str(e)}")
|
@@ -339,19 +303,25 @@ def monitor_feed() -> Tuple[
|
|
339 |
# Update detection metrics
|
340 |
metrics = update_metrics(all_detected_items)
|
341 |
|
342 |
-
# Generate GPS coordinates
|
343 |
gps_coord = [17.385044 + random.uniform(-0.001, 0.001), 78.486671 + frame_count * 0.0001]
|
344 |
gps_coordinates.append(gps_coord)
|
345 |
|
346 |
-
# Save frame if detections are present
|
347 |
detection_types = {item.get("type") for item in all_detected_items if "type" in item}
|
348 |
if detection_types:
|
349 |
try:
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
if
|
354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
except Exception as e:
|
356 |
log_entries.append(f"Error saving captured frame: {str(e)}")
|
357 |
logging.error(f"Error saving captured frame: {str(e)}")
|
@@ -386,7 +356,7 @@ def monitor_feed() -> Tuple[
|
|
386 |
last_frame = frame.copy()
|
387 |
last_detections = metrics
|
388 |
|
389 |
-
# Track cracks for metrics (
|
390 |
crack_detected = len([item for item in all_detected_items if item.get("type") == "crack"]) if active_service == "operations_maintenance" else 0
|
391 |
if active_service == "operations_maintenance":
|
392 |
crack_severity_all.extend([
|
@@ -417,10 +387,10 @@ def monitor_feed() -> Tuple[
|
|
417 |
if len(crack_severity_all) > 500:
|
418 |
crack_severity_all.pop(0)
|
419 |
|
420 |
-
# Resize frame
|
421 |
-
frame = cv2.resize(last_frame, (
|
422 |
-
cv2.putText(frame, f"Frame: {frame_count}", (10,
|
423 |
-
cv2.putText(frame, f"{last_timestamp}", (10,
|
424 |
|
425 |
# Generate charts for Operations Maintenance only
|
426 |
line_chart = None
|
@@ -479,7 +449,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
|
|
479 |
|
480 |
with gr.Row():
|
481 |
with gr.Column(scale=3):
|
482 |
-
video_output = gr.Image(label="Live Drone Feed", width=
|
483 |
with gr.Column(scale=1):
|
484 |
detections_output = gr.Textbox(
|
485 |
label="Detection Metrics",
|
@@ -497,42 +467,71 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
|
|
497 |
bar_output = gr.Plot(label="Severity Distribution (Operations Maintenance Only)")
|
498 |
|
499 |
with gr.Row():
|
500 |
-
captured_images = gr.Gallery(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
501 |
|
502 |
with gr.Row():
|
503 |
pause_btn = gr.Button("鈴革笍 Pause", variant="secondary")
|
504 |
resume_btn = gr.Button("鈻讹笍 Resume", variant="primary")
|
505 |
-
frame_slider = gr.Slider(0.
|
506 |
|
507 |
gr.HTML("""
|
508 |
<style>
|
509 |
#live-feed {
|
510 |
border: 2px solid #4682B4;
|
511 |
border-radius: 10px;
|
|
|
512 |
}
|
513 |
.gr-button-primary {
|
514 |
background-color: #4682B4 !important;
|
|
|
|
|
|
|
|
|
|
|
515 |
}
|
516 |
.gr-button-secondary {
|
517 |
background-color: #FF6347 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
518 |
}
|
519 |
</style>
|
520 |
""")
|
521 |
|
522 |
def toggle_pause() -> str:
|
523 |
-
"""Pause the video stream."""
|
524 |
global paused
|
525 |
paused = True
|
526 |
return "**Status:** 鈴革笍 Paused"
|
527 |
|
528 |
def toggle_resume() -> str:
|
529 |
-
"""Resume the video stream."""
|
530 |
global paused
|
531 |
paused = False
|
532 |
return "**Status:** 馃煝 Streaming"
|
533 |
|
534 |
def set_frame_rate(val: float) -> None:
|
535 |
-
"""Set the frame rate for streaming."""
|
536 |
global frame_rate
|
537 |
frame_rate = val
|
538 |
|
@@ -545,18 +544,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
|
|
545 |
)
|
546 |
|
547 |
def update_toggles(uc_val: bool, om_val: bool, rs_val: bool, pl_val: bool) -> Tuple[str, str, str, str, str]:
|
548 |
-
"""
|
549 |
-
Update toggle states and set the active service.
|
550 |
-
|
551 |
-
Args:
|
552 |
-
uc_val (bool): Under Construction toggle value.
|
553 |
-
om_val (bool): Operations Maintenance toggle value.
|
554 |
-
rs_val (bool): Road Safety toggle value.
|
555 |
-
pl_val (bool): Plantation toggle value.
|
556 |
-
|
557 |
-
Returns:
|
558 |
-
Tuple[str, str, str, str, str]: Status updates for each toggle and overall status message.
|
559 |
-
"""
|
560 |
active, status_message = set_active_service("toggle", uc_val, om_val, rs_val, pl_val)
|
561 |
uc_status_val = "Enabled" if active == "under_construction" else "Disabled"
|
562 |
om_status_val = "Enabled" if active == "operations_maintenance" else "Disabled"
|
@@ -578,7 +565,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
|
|
578 |
frame_slider.change(set_frame_rate, inputs=[frame_slider])
|
579 |
|
580 |
def streaming_loop():
|
581 |
-
"""Continuous loop to stream video and process frames."""
|
582 |
while True:
|
583 |
if not video_loaded:
|
584 |
yield None, json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2), "\n".join(log_entries[-10:]), None, None, None, last_detected_images
|
|
|
5 |
import json
|
6 |
import random
|
7 |
import logging
|
8 |
+
import numpy as np
|
9 |
from datetime import datetime
|
10 |
from collections import Counter
|
11 |
from typing import Any, Dict, List, Optional, Tuple
|
12 |
+
import matplotlib.pyplot as plt
|
13 |
+
from matplotlib import font_manager
|
14 |
|
15 |
# Suppress Ultralytics warning by setting a writable config directory
|
16 |
os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
|
|
|
54 |
|
55 |
# Global variables
|
56 |
paused: bool = False
|
57 |
+
frame_rate: float = 0.05 # Reduced for faster processing
|
58 |
frame_count: int = 0
|
59 |
log_entries: List[str] = []
|
60 |
+
crack_counts: List[int] = []
|
61 |
+
crack_severity_all: List[str] = []
|
62 |
last_frame: Optional[np.ndarray] = None
|
63 |
last_detections: Dict[str, Any] = {}
|
64 |
last_timestamp: str = ""
|
65 |
last_detected_images: List[str] = []
|
66 |
+
detected_image_set: set = set() # To avoid duplicates
|
67 |
gps_coordinates: List[List[float]] = []
|
68 |
video_loaded: bool = False
|
69 |
active_service: Optional[str] = None
|
|
|
77 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
78 |
|
79 |
def initialize_video(video_file: Optional[Any] = None) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
global video_loaded, log_entries
|
81 |
release_video()
|
82 |
video_path = DEFAULT_VIDEO_PATH
|
|
|
99 |
rs_val: bool,
|
100 |
pl_val: bool
|
101 |
) -> Tuple[Optional[str], str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
global active_service
|
103 |
toggles = {
|
104 |
"under_construction": uc_val,
|
|
|
126 |
return None, "No Service Category Enabled"
|
127 |
|
128 |
def generate_crack_trend_chart() -> Optional[plt.Figure]:
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
if not crack_counts:
|
130 |
return None
|
131 |
|
132 |
data = crack_counts[-50:]
|
133 |
labels = list(range(len(data)))
|
134 |
|
135 |
+
fig, ax = plt.subplots(figsize=(6, 4), facecolor='#f0f4f8')
|
136 |
+
ax.plot(labels, data, color="#FF6347", linewidth=2.5, label="Cracks Over Time", marker='o', markersize=5)
|
137 |
+
ax.set_title("Crack Trend (Operations Maintenance)", fontsize=14, pad=15, fontweight='bold', color='#333333')
|
138 |
+
ax.set_xlabel("Frame", fontsize=12, color='#333333')
|
139 |
+
ax.set_ylabel("Count", fontsize=12, color='#333333')
|
140 |
ax.set_ylim(bottom=0)
|
141 |
+
ax.grid(True, linestyle='--', alpha=0.7)
|
142 |
+
ax.set_facecolor('#ffffff')
|
143 |
+
ax.legend(frameon=True, facecolor='#ffffff', edgecolor='#333333')
|
144 |
+
for spine in ax.spines.values():
|
145 |
+
spine.set_edgecolor('#333333')
|
146 |
plt.tight_layout()
|
147 |
return fig
|
148 |
|
149 |
def generate_crack_severity_chart() -> Optional[plt.Figure]:
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
if not crack_severity_all:
|
151 |
return None
|
152 |
|
|
|
154 |
labels = list(count.keys())
|
155 |
sizes = list(count.values())
|
156 |
|
157 |
+
fig, ax = plt.subplots(figsize=(6, 4), facecolor='#f0f4f8')
|
158 |
+
colors = ['#FF6347', '#4682B4', '#FFD700']
|
159 |
+
wedges, texts, autotexts = ax.pie(
|
160 |
sizes,
|
161 |
labels=labels,
|
162 |
+
colors=colors,
|
163 |
+
autopct='%1.1f%%',
|
164 |
+
startangle=90,
|
165 |
+
shadow=True,
|
166 |
+
textprops={'fontsize': 10, 'color': '#333333'}
|
167 |
)
|
168 |
+
ax.set_title("Crack Severity (Operations Maintenance)", fontsize=14, pad=15, fontweight='bold', color='#333333')
|
169 |
+
for w in wedges:
|
170 |
+
w.set_edgecolor('#333333')
|
171 |
plt.tight_layout()
|
172 |
return fig
|
173 |
|
174 |
def generate_severity_distribution_chart() -> Optional[plt.Figure]:
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
if not crack_severity_all:
|
176 |
return None
|
177 |
|
|
|
179 |
labels = list(count.keys())
|
180 |
sizes = list(count.values())
|
181 |
|
182 |
+
fig, ax = plt.subplots(figsize=(6, 4), facecolor='#f0f4f8')
|
183 |
+
colors = ['#FF6347', '#4682B4', '#FFD700']
|
184 |
+
bars = ax.bar(labels, sizes, color=colors, edgecolor='#333333')
|
185 |
+
ax.set_title("Severity Distribution (Operations Maintenance)", fontsize=14, pad=15, fontweight='bold', color='#333333')
|
186 |
+
ax.set_xlabel("Severity", fontsize=12, color='#333333')
|
187 |
+
ax.set_ylabel("Count", fontsize=12, color='#333333')
|
188 |
ax.set_ylim(bottom=0)
|
189 |
+
ax.set_facecolor('#ffffff')
|
190 |
+
for bar in bars:
|
191 |
+
height = bar.get_height()
|
192 |
+
ax.text(bar.get_x() + bar.get_width()/2, height, f'{int(height)}', ha='center', va='bottom', fontsize=10, color='#333333')
|
193 |
+
for spine in ax.spines.values():
|
194 |
+
spine.set_edgecolor('#333333')
|
195 |
plt.tight_layout()
|
196 |
return fig
|
197 |
|
|
|
204 |
Optional[plt.Figure],
|
205 |
List[str]
|
206 |
]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
global paused, frame_count, last_frame, last_detections, last_timestamp
|
208 |
+
global gps_coordinates, last_detected_images, video_loaded, detected_image_set
|
209 |
|
210 |
if not video_loaded:
|
211 |
log_entries.append("Cannot start streaming: Video not loaded successfully.")
|
|
|
281 |
frame = shadow_results["frame"]
|
282 |
all_detected_items.extend(shadow_dets)
|
283 |
|
284 |
+
# Skip thermal processing to improve speed (unless grayscale frame is detected)
|
285 |
if len(frame.shape) == 2:
|
286 |
thermal_results = process_thermal(frame)
|
287 |
thermal_dets = thermal_results["detections"]
|
|
|
293 |
logging.error(f"Processing error in {active_service}: {str(e)}")
|
294 |
all_detected_items = []
|
295 |
|
296 |
+
# Save temporary image for display (lower quality for speed)
|
297 |
try:
|
298 |
+
cv2.imwrite(TEMP_IMAGE_PATH, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 80])
|
299 |
except Exception as e:
|
300 |
log_entries.append(f"Error saving temp image: {str(e)}")
|
301 |
logging.error(f"Error saving temp image: {str(e)}")
|
|
|
303 |
# Update detection metrics
|
304 |
metrics = update_metrics(all_detected_items)
|
305 |
|
306 |
+
# Generate GPS coordinates
|
307 |
gps_coord = [17.385044 + random.uniform(-0.001, 0.001), 78.486671 + frame_count * 0.0001]
|
308 |
gps_coordinates.append(gps_coord)
|
309 |
|
310 |
+
# Save frame if detections are present (avoid duplicates)
|
311 |
detection_types = {item.get("type") for item in all_detected_items if "type" in item}
|
312 |
if detection_types:
|
313 |
try:
|
314 |
+
# Create a unique identifier for the frame based on detections
|
315 |
+
detection_key = tuple(sorted([(item["type"], tuple(item["coordinates"])) for item in all_detected_items]))
|
316 |
+
detection_key_str = str(detection_key)
|
317 |
+
if detection_key_str not in detected_image_set:
|
318 |
+
captured_frame_path = os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count}.jpg")
|
319 |
+
cv2.imwrite(captured_frame_path, frame)
|
320 |
+
last_detected_images.insert(0, captured_frame_path) # Add to the beginning (newest first)
|
321 |
+
detected_image_set.add(detection_key_str)
|
322 |
+
if len(last_detected_images) > 100:
|
323 |
+
old_image = last_detected_images.pop()
|
324 |
+
detected_image_set.discard(str(old_image))
|
325 |
except Exception as e:
|
326 |
log_entries.append(f"Error saving captured frame: {str(e)}")
|
327 |
logging.error(f"Error saving captured frame: {str(e)}")
|
|
|
356 |
last_frame = frame.copy()
|
357 |
last_detections = metrics
|
358 |
|
359 |
+
# Track cracks for metrics (Operations Maintenance only)
|
360 |
crack_detected = len([item for item in all_detected_items if item.get("type") == "crack"]) if active_service == "operations_maintenance" else 0
|
361 |
if active_service == "operations_maintenance":
|
362 |
crack_severity_all.extend([
|
|
|
387 |
if len(crack_severity_all) > 500:
|
388 |
crack_severity_all.pop(0)
|
389 |
|
390 |
+
# Resize frame for display (smaller size for speed)
|
391 |
+
frame = cv2.resize(last_frame, (320, 240))
|
392 |
+
cv2.putText(frame, f"Frame: {frame_count}", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
|
393 |
+
cv2.putText(frame, f"{last_timestamp}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
|
394 |
|
395 |
# Generate charts for Operations Maintenance only
|
396 |
line_chart = None
|
|
|
449 |
|
450 |
with gr.Row():
|
451 |
with gr.Column(scale=3):
|
452 |
+
video_output = gr.Image(label="Live Drone Feed", width=320, height=240, elem_id="live-feed")
|
453 |
with gr.Column(scale=1):
|
454 |
detections_output = gr.Textbox(
|
455 |
label="Detection Metrics",
|
|
|
467 |
bar_output = gr.Plot(label="Severity Distribution (Operations Maintenance Only)")
|
468 |
|
469 |
with gr.Row():
|
470 |
+
captured_images = gr.Gallery(
|
471 |
+
label="Detected Frames (Last 100+)",
|
472 |
+
columns=4,
|
473 |
+
rows=5,
|
474 |
+
height="auto",
|
475 |
+
object_fit="contain",
|
476 |
+
preview=True
|
477 |
+
)
|
478 |
|
479 |
with gr.Row():
|
480 |
pause_btn = gr.Button("鈴革笍 Pause", variant="secondary")
|
481 |
resume_btn = gr.Button("鈻讹笍 Resume", variant="primary")
|
482 |
+
frame_slider = gr.Slider(0.01, 1.0, value=0.05, label="Frame Interval (seconds)", step=0.01)
|
483 |
|
484 |
gr.HTML("""
|
485 |
<style>
|
486 |
#live-feed {
|
487 |
border: 2px solid #4682B4;
|
488 |
border-radius: 10px;
|
489 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
490 |
}
|
491 |
.gr-button-primary {
|
492 |
background-color: #4682B4 !important;
|
493 |
+
border-radius: 5px;
|
494 |
+
transition: background-color 0.3s ease;
|
495 |
+
}
|
496 |
+
.gr-button-primary:hover {
|
497 |
+
background-color: #5a9bd4 !important;
|
498 |
}
|
499 |
.gr-button-secondary {
|
500 |
background-color: #FF6347 !important;
|
501 |
+
border-radius: 5px;
|
502 |
+
transition: background-color 0.3s ease;
|
503 |
+
}
|
504 |
+
.gr-button-secondary:hover {
|
505 |
+
background-color: #ff826b !important;
|
506 |
+
}
|
507 |
+
.gr-plot {
|
508 |
+
background-color: #f0f4f8;
|
509 |
+
border-radius: 10px;
|
510 |
+
padding: 10px;
|
511 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
512 |
+
}
|
513 |
+
.gr-gallery img {
|
514 |
+
border: 1px solid #4682B4;
|
515 |
+
border-radius: 5px;
|
516 |
+
transition: transform 0.3s ease;
|
517 |
+
}
|
518 |
+
.gr-gallery img:hover {
|
519 |
+
transform: scale(1.05);
|
520 |
}
|
521 |
</style>
|
522 |
""")
|
523 |
|
524 |
def toggle_pause() -> str:
|
|
|
525 |
global paused
|
526 |
paused = True
|
527 |
return "**Status:** 鈴革笍 Paused"
|
528 |
|
529 |
def toggle_resume() -> str:
|
|
|
530 |
global paused
|
531 |
paused = False
|
532 |
return "**Status:** 馃煝 Streaming"
|
533 |
|
534 |
def set_frame_rate(val: float) -> None:
|
|
|
535 |
global frame_rate
|
536 |
frame_rate = val
|
537 |
|
|
|
544 |
)
|
545 |
|
546 |
def update_toggles(uc_val: bool, om_val: bool, rs_val: bool, pl_val: bool) -> Tuple[str, str, str, str, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
547 |
active, status_message = set_active_service("toggle", uc_val, om_val, rs_val, pl_val)
|
548 |
uc_status_val = "Enabled" if active == "under_construction" else "Disabled"
|
549 |
om_status_val = "Enabled" if active == "operations_maintenance" else "Disabled"
|
|
|
565 |
frame_slider.change(set_frame_rate, inputs=[frame_slider])
|
566 |
|
567 |
def streaming_loop():
|
|
|
568 |
while True:
|
569 |
if not video_loaded:
|
570 |
yield None, json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2), "\n".join(log_entries[-10:]), None, None, None, last_detected_images
|