Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -5,12 +5,11 @@ import time
|
|
5 |
import json
|
6 |
import random
|
7 |
import logging
|
8 |
-
import
|
9 |
from datetime import datetime
|
10 |
from collections import Counter
|
11 |
from typing import Any, Dict, List, Optional, Tuple
|
12 |
-
import
|
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"
|
@@ -21,15 +20,16 @@ try:
|
|
21 |
from services.detection_service import process_frame as process_generic
|
22 |
from services.metrics_service import update_metrics
|
23 |
from services.overlay_service import overlay_boxes
|
24 |
-
from services.salesforce_dispatcher import
|
25 |
-
from services.shadow_detection import
|
26 |
from services.thermal_service import process_thermal
|
|
|
27 |
# Under Construction services
|
28 |
from services.under_construction.earthwork_detection import process_earthwork
|
29 |
from services.under_construction.culvert_check import process_culverts
|
30 |
from services.under_construction.bridge_pier_check import process_bridge_piers
|
31 |
# Operations Maintenance services
|
32 |
-
from services.operations_maintenance.crack_detection import
|
33 |
from services.operations_maintenance.pothole_detection import process_potholes
|
34 |
from services.operations_maintenance.signage_check import process_signages
|
35 |
# Road Safety services
|
@@ -54,16 +54,16 @@ logging.basicConfig(
|
|
54 |
|
55 |
# Global variables
|
56 |
paused: bool = False
|
57 |
-
frame_rate: float = 0.
|
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 |
-
|
64 |
last_timestamp: str = ""
|
65 |
-
|
66 |
-
|
67 |
gps_coordinates: List[List[float]] = []
|
68 |
video_loaded: bool = False
|
69 |
active_service: Optional[str] = None
|
@@ -86,11 +86,19 @@ def initialize_video(video_file: Optional[Any] = None) -> str:
|
|
86 |
log_entries.append(f"Using uploaded video: {video_path}")
|
87 |
logging.info(f"Using uploaded video: {video_path}")
|
88 |
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
95 |
def set_active_service(
|
96 |
service_name: str,
|
@@ -125,87 +133,47 @@ def set_active_service(
|
|
125 |
logging.info("No service category enabled.")
|
126 |
return None, "No Service Category Enabled"
|
127 |
|
128 |
-
def
|
129 |
if not crack_counts:
|
130 |
return None
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
ax.
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
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 |
-
|
153 |
count = Counter(crack_severity_all[-200:])
|
154 |
-
labels =
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
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 |
-
|
178 |
-
count = Counter(crack_severity_all[-200:])
|
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 |
|
198 |
def monitor_feed() -> Tuple[
|
199 |
Optional[np.ndarray],
|
200 |
str,
|
201 |
str,
|
202 |
-
|
203 |
-
|
204 |
-
Optional[
|
205 |
-
|
206 |
]:
|
207 |
-
global paused, frame_count, last_frame,
|
208 |
-
global gps_coordinates,
|
209 |
|
210 |
if not video_loaded:
|
211 |
log_entries.append("Cannot start streaming: Video not loaded successfully.")
|
@@ -214,15 +182,15 @@ def monitor_feed() -> Tuple[
|
|
214 |
None,
|
215 |
json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2),
|
216 |
"\n".join(log_entries[-10:]),
|
|
|
|
|
217 |
None,
|
218 |
-
None
|
219 |
-
None,
|
220 |
-
last_detected_images
|
221 |
)
|
222 |
|
223 |
if paused and last_frame is not None:
|
224 |
frame = last_frame.copy()
|
225 |
-
|
226 |
else:
|
227 |
try:
|
228 |
frame = get_next_video_frame()
|
@@ -233,15 +201,17 @@ def monitor_feed() -> Tuple[
|
|
233 |
logging.error(f"Frame retrieval error: {str(e)}")
|
234 |
return (
|
235 |
None,
|
236 |
-
json.dumps(
|
237 |
"\n".join(log_entries[-10:]),
|
|
|
|
|
238 |
None,
|
239 |
-
None
|
240 |
-
None,
|
241 |
-
last_detected_images
|
242 |
)
|
243 |
|
244 |
all_detected_items: List[Dict[str, Any]] = []
|
|
|
|
|
245 |
|
246 |
try:
|
247 |
# Process frame based on active service
|
@@ -250,56 +220,48 @@ def monitor_feed() -> Tuple[
|
|
250 |
culvert_dets, frame = process_culverts(frame)
|
251 |
bridge_pier_dets, frame = process_bridge_piers(frame)
|
252 |
all_detected_items.extend(earthwork_dets + culvert_dets + bridge_pier_dets)
|
253 |
-
|
254 |
elif active_service == "operations_maintenance":
|
255 |
-
|
256 |
-
frame = overlay_boxes(frame, crack_items)
|
257 |
pothole_dets, frame = process_potholes(frame)
|
258 |
signage_dets, frame = process_signages(frame)
|
259 |
-
all_detected_items.extend(
|
260 |
-
|
261 |
elif active_service == "road_safety":
|
262 |
barrier_dets, frame = process_barriers(frame)
|
263 |
lighting_dets, frame = process_lighting(frame)
|
264 |
accident_dets, frame = process_accident_spots(frame)
|
265 |
pothole_crack_dets, frame = detect_potholes_and_cracks(frame)
|
266 |
all_detected_items.extend(barrier_dets + lighting_dets + accident_dets + pothole_crack_dets)
|
267 |
-
|
268 |
elif active_service == "plantation":
|
269 |
plant_dets, frame = process_plants(frame)
|
270 |
health_dets, frame = process_plant_health(frame)
|
271 |
missing_dets, frame = process_missing_patches(frame)
|
272 |
all_detected_items.extend(plant_dets + health_dets + missing_dets)
|
273 |
-
|
274 |
else:
|
275 |
generic_dets, frame = process_generic(frame)
|
276 |
all_detected_items.extend(generic_dets)
|
277 |
|
278 |
# Apply shadow detection
|
279 |
-
|
280 |
-
shadow_dets = shadow_results["detections"]
|
281 |
-
frame = shadow_results["frame"]
|
282 |
-
all_detected_items.extend(shadow_dets)
|
283 |
|
284 |
-
#
|
285 |
if len(frame.shape) == 2:
|
286 |
thermal_results = process_thermal(frame)
|
287 |
thermal_dets = thermal_results["detections"]
|
288 |
frame = thermal_results["frame"]
|
289 |
all_detected_items.extend(thermal_dets)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
|
291 |
except Exception as e:
|
292 |
log_entries.append(f"Processing Error: {str(e)}")
|
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)}")
|
302 |
-
|
303 |
# Update detection metrics
|
304 |
metrics = update_metrics(all_detected_items)
|
305 |
|
@@ -307,42 +269,44 @@ def monitor_feed() -> Tuple[
|
|
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
|
311 |
detection_types = {item.get("type") for item in all_detected_items if "type" in item}
|
312 |
if detection_types:
|
313 |
try:
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
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)}")
|
328 |
|
329 |
# Prepare data for Salesforce dispatch
|
330 |
all_detections = {
|
331 |
-
"
|
332 |
"metrics": metrics,
|
333 |
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
334 |
"frame_count": frame_count,
|
335 |
-
"gps_coordinates": gps_coord
|
|
|
|
|
336 |
}
|
337 |
|
338 |
# Dispatch to Salesforce
|
339 |
try:
|
340 |
-
|
341 |
except Exception as e:
|
342 |
log_entries.append(f"Salesforce Dispatch Error: {str(e)}")
|
343 |
logging.error(f"Salesforce dispatch error: {str(e)}")
|
344 |
|
345 |
-
# Save processed frame
|
346 |
try:
|
347 |
frame_path = os.path.join(OUTPUT_DIR, f"frame_{frame_count:04d}.jpg")
|
348 |
cv2.imwrite(frame_path, frame)
|
@@ -354,30 +318,23 @@ def monitor_feed() -> Tuple[
|
|
354 |
frame_count += 1
|
355 |
last_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
356 |
last_frame = frame.copy()
|
357 |
-
|
358 |
|
359 |
-
# Track cracks for metrics
|
360 |
-
crack_detected = len([item for item in all_detected_items if item.get("type") == "crack"])
|
361 |
-
if
|
|
|
362 |
crack_severity_all.extend([
|
363 |
item["severity"]
|
364 |
for item in all_detected_items
|
365 |
-
if item.get("type")
|
366 |
])
|
367 |
-
|
368 |
-
# Add crack trend and severity to metrics
|
369 |
-
if active_service == "operations_maintenance":
|
370 |
-
last_detections["crack_count_last_50_frames"] = crack_counts[-50:] if crack_counts else []
|
371 |
-
severity_counts = Counter(crack_severity_all[-200:]) if crack_severity_all else {}
|
372 |
-
last_detections["crack_severity_distribution"] = dict(severity_counts)
|
373 |
|
374 |
# Log frame processing details
|
375 |
-
log_message = f"{last_timestamp} - Frame {frame_count} -
|
376 |
-
if crack_detected:
|
377 |
-
log_message += f" - Cracks: {crack_detected}"
|
378 |
log_entries.append(log_message)
|
379 |
logging.info(log_message)
|
380 |
-
crack_counts.append(crack_detected)
|
381 |
|
382 |
# Limit the size of logs and crack data
|
383 |
if len(log_entries) > 100:
|
@@ -387,28 +344,22 @@ def monitor_feed() -> Tuple[
|
|
387 |
if len(crack_severity_all) > 500:
|
388 |
crack_severity_all.pop(0)
|
389 |
|
390 |
-
# Resize frame
|
391 |
-
frame = cv2.resize(last_frame, (
|
392 |
-
cv2.putText(frame, f"Frame: {frame_count}", (10,
|
393 |
-
cv2.putText(frame, f"{last_timestamp}", (10,
|
394 |
|
395 |
-
# Generate
|
396 |
-
|
397 |
-
pie_chart = None
|
398 |
-
bar_chart = None
|
399 |
-
if active_service == "operations_maintenance":
|
400 |
-
line_chart = generate_crack_trend_chart()
|
401 |
-
pie_chart = generate_crack_severity_chart()
|
402 |
-
bar_chart = generate_severity_distribution_chart()
|
403 |
|
404 |
return (
|
405 |
frame[:, :, ::-1], # Convert BGR to RGB for Gradio
|
406 |
-
json.dumps(
|
407 |
"\n".join(log_entries[-10:]),
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
)
|
413 |
|
414 |
# Gradio UI setup
|
@@ -449,74 +400,42 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
|
|
449 |
|
450 |
with gr.Row():
|
451 |
with gr.Column(scale=3):
|
452 |
-
video_output = gr.Image(label="Live Drone Feed", width=
|
453 |
with gr.Column(scale=1):
|
454 |
-
|
455 |
label="Detection Metrics",
|
456 |
lines=10,
|
457 |
interactive=False,
|
458 |
-
placeholder="Detection metrics, crack
|
459 |
)
|
460 |
|
461 |
with gr.Row():
|
462 |
with gr.Column(scale=2):
|
463 |
logs_output = gr.Textbox(label="Live Logs", lines=8, interactive=False)
|
464 |
with gr.Column(scale=1):
|
465 |
-
|
466 |
-
|
467 |
-
bar_output = gr.Plot(label="Severity Distribution (Operations Maintenance Only)")
|
468 |
|
469 |
with gr.Row():
|
470 |
-
|
471 |
-
|
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.
|
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 |
""")
|
@@ -567,16 +486,16 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
|
|
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:]),
|
571 |
else:
|
572 |
-
frame,
|
573 |
if frame is None:
|
574 |
-
yield None,
|
575 |
else:
|
576 |
-
yield frame,
|
577 |
time.sleep(frame_rate)
|
578 |
|
579 |
-
app.load(streaming_loop, outputs=[video_output,
|
580 |
|
581 |
if __name__ == "__main__":
|
582 |
app.launch(share=False)
|
|
|
5 |
import json
|
6 |
import random
|
7 |
import logging
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
from datetime import datetime
|
10 |
from collections import Counter
|
11 |
from typing import Any, Dict, List, Optional, Tuple
|
12 |
+
import numpy as np
|
|
|
13 |
|
14 |
# Suppress Ultralytics warning by setting a writable config directory
|
15 |
os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
|
|
|
20 |
from services.detection_service import process_frame as process_generic
|
21 |
from services.metrics_service import update_metrics
|
22 |
from services.overlay_service import overlay_boxes
|
23 |
+
from services.salesforce_dispatcher import send_to_salesforce
|
24 |
+
from services.shadow_detection import detect_shadow_coverage
|
25 |
from services.thermal_service import process_thermal
|
26 |
+
from services.map_service import generate_map
|
27 |
# Under Construction services
|
28 |
from services.under_construction.earthwork_detection import process_earthwork
|
29 |
from services.under_construction.culvert_check import process_culverts
|
30 |
from services.under_construction.bridge_pier_check import process_bridge_piers
|
31 |
# Operations Maintenance services
|
32 |
+
from services.operations_maintenance.crack_detection import detect_cracks_and_holes
|
33 |
from services.operations_maintenance.pothole_detection import process_potholes
|
34 |
from services.operations_maintenance.signage_check import process_signages
|
35 |
# Road Safety services
|
|
|
54 |
|
55 |
# Global variables
|
56 |
paused: bool = False
|
57 |
+
frame_rate: float = 0.3
|
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_metrics: Dict[str, Any] = {}
|
64 |
last_timestamp: str = ""
|
65 |
+
last_detected_cracks: List[str] = []
|
66 |
+
last_detected_holes: List[str] = []
|
67 |
gps_coordinates: List[List[float]] = []
|
68 |
video_loaded: bool = False
|
69 |
active_service: Optional[str] = None
|
|
|
86 |
log_entries.append(f"Using uploaded video: {video_path}")
|
87 |
logging.info(f"Using uploaded video: {video_path}")
|
88 |
|
89 |
+
try:
|
90 |
+
preload_video()
|
91 |
+
video_loaded = True
|
92 |
+
status = f"Successfully loaded video: {video_path}"
|
93 |
+
log_entries.append(status)
|
94 |
+
logging.info(status)
|
95 |
+
return status
|
96 |
+
except Exception as e:
|
97 |
+
video_loaded = False
|
98 |
+
status = f"Error loading video: {str(e)}"
|
99 |
+
log_entries.append(status)
|
100 |
+
logging.error(status)
|
101 |
+
return status
|
102 |
|
103 |
def set_active_service(
|
104 |
service_name: str,
|
|
|
133 |
logging.info("No service category enabled.")
|
134 |
return None, "No Service Category Enabled"
|
135 |
|
136 |
+
def generate_line_chart() -> Optional[str]:
|
137 |
if not crack_counts:
|
138 |
return None
|
139 |
+
fig, ax = plt.subplots(figsize=(4, 2))
|
140 |
+
ax.plot(crack_counts[-50:], marker='o', color='#4682B4')
|
141 |
+
ax.set_title("Cracks/Holes Over Time")
|
142 |
+
ax.set_xlabel("Frame")
|
143 |
+
ax.set_ylabel("Count")
|
144 |
+
ax.grid(True)
|
145 |
+
fig.tight_layout()
|
146 |
+
chart_path = "chart_temp.png"
|
147 |
+
fig.savefig(chart_path)
|
148 |
+
plt.close(fig)
|
149 |
+
return chart_path
|
150 |
+
|
151 |
+
def generate_pie_chart() -> Optional[str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
if not crack_severity_all:
|
153 |
return None
|
154 |
+
fig, ax = plt.subplots(figsize=(4, 2))
|
155 |
count = Counter(crack_severity_all[-200:])
|
156 |
+
labels, sizes = zip(*count.items())
|
157 |
+
colors = ['#FF6347', '#FFA500', '#32CD32', '#800080']
|
158 |
+
ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=140, colors=colors[:len(labels)])
|
159 |
+
ax.axis('equal')
|
160 |
+
fig.tight_layout()
|
161 |
+
pie_path = "pie_temp.png"
|
162 |
+
fig.savefig(pie_path)
|
163 |
+
plt.close(fig)
|
164 |
+
return pie_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
def monitor_feed() -> Tuple[
|
167 |
Optional[np.ndarray],
|
168 |
str,
|
169 |
str,
|
170 |
+
List[str],
|
171 |
+
List[str],
|
172 |
+
Optional[str],
|
173 |
+
Optional[str]
|
174 |
]:
|
175 |
+
global paused, frame_count, last_frame, last_metrics, last_timestamp
|
176 |
+
global gps_coordinates, last_detected_cracks, last_detected_holes, video_loaded
|
177 |
|
178 |
if not video_loaded:
|
179 |
log_entries.append("Cannot start streaming: Video not loaded successfully.")
|
|
|
182 |
None,
|
183 |
json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2),
|
184 |
"\n".join(log_entries[-10:]),
|
185 |
+
last_detected_cracks,
|
186 |
+
last_detected_holes,
|
187 |
None,
|
188 |
+
None
|
|
|
|
|
189 |
)
|
190 |
|
191 |
if paused and last_frame is not None:
|
192 |
frame = last_frame.copy()
|
193 |
+
metrics = last_metrics.copy()
|
194 |
else:
|
195 |
try:
|
196 |
frame = get_next_video_frame()
|
|
|
201 |
logging.error(f"Frame retrieval error: {str(e)}")
|
202 |
return (
|
203 |
None,
|
204 |
+
json.dumps(last_metrics, indent=2),
|
205 |
"\n".join(log_entries[-10:]),
|
206 |
+
last_detected_cracks,
|
207 |
+
last_detected_holes,
|
208 |
None,
|
209 |
+
None
|
|
|
|
|
210 |
)
|
211 |
|
212 |
all_detected_items: List[Dict[str, Any]] = []
|
213 |
+
shadow_issue = False
|
214 |
+
thermal_flag = False
|
215 |
|
216 |
try:
|
217 |
# Process frame based on active service
|
|
|
220 |
culvert_dets, frame = process_culverts(frame)
|
221 |
bridge_pier_dets, frame = process_bridge_piers(frame)
|
222 |
all_detected_items.extend(earthwork_dets + culvert_dets + bridge_pier_dets)
|
|
|
223 |
elif active_service == "operations_maintenance":
|
224 |
+
crack_hole_dets, frame = detect_cracks_and_holes(frame)
|
|
|
225 |
pothole_dets, frame = process_potholes(frame)
|
226 |
signage_dets, frame = process_signages(frame)
|
227 |
+
all_detected_items.extend(crack_hole_dets + pothole_dets + signage_dets)
|
|
|
228 |
elif active_service == "road_safety":
|
229 |
barrier_dets, frame = process_barriers(frame)
|
230 |
lighting_dets, frame = process_lighting(frame)
|
231 |
accident_dets, frame = process_accident_spots(frame)
|
232 |
pothole_crack_dets, frame = detect_potholes_and_cracks(frame)
|
233 |
all_detected_items.extend(barrier_dets + lighting_dets + accident_dets + pothole_crack_dets)
|
|
|
234 |
elif active_service == "plantation":
|
235 |
plant_dets, frame = process_plants(frame)
|
236 |
health_dets, frame = process_plant_health(frame)
|
237 |
missing_dets, frame = process_missing_patches(frame)
|
238 |
all_detected_items.extend(plant_dets + health_dets + missing_dets)
|
|
|
239 |
else:
|
240 |
generic_dets, frame = process_generic(frame)
|
241 |
all_detected_items.extend(generic_dets)
|
242 |
|
243 |
# Apply shadow detection
|
244 |
+
shadow_issue = detect_shadow_coverage(TEMP_IMAGE_PATH)
|
|
|
|
|
|
|
245 |
|
246 |
+
# Apply thermal processing if frame is grayscale
|
247 |
if len(frame.shape) == 2:
|
248 |
thermal_results = process_thermal(frame)
|
249 |
thermal_dets = thermal_results["detections"]
|
250 |
frame = thermal_results["frame"]
|
251 |
all_detected_items.extend(thermal_dets)
|
252 |
+
thermal_flag = bool(thermal_dets)
|
253 |
+
|
254 |
+
# Overlay detections
|
255 |
+
frame = overlay_boxes(frame, all_detected_items)
|
256 |
+
|
257 |
+
# Save temporary image
|
258 |
+
cv2.imwrite(TEMP_IMAGE_PATH, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
|
259 |
|
260 |
except Exception as e:
|
261 |
log_entries.append(f"Processing Error: {str(e)}")
|
262 |
logging.error(f"Processing error in {active_service}: {str(e)}")
|
263 |
all_detected_items = []
|
264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
# Update detection metrics
|
266 |
metrics = update_metrics(all_detected_items)
|
267 |
|
|
|
269 |
gps_coord = [17.385044 + random.uniform(-0.001, 0.001), 78.486671 + frame_count * 0.0001]
|
270 |
gps_coordinates.append(gps_coord)
|
271 |
|
272 |
+
# Save frame if detections are present
|
273 |
detection_types = {item.get("type") for item in all_detected_items if "type" in item}
|
274 |
if detection_types:
|
275 |
try:
|
276 |
+
captured_frame_path = os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count}.jpg")
|
277 |
+
cv2.imwrite(captured_frame_path, frame)
|
278 |
+
for item in all_detected_items:
|
279 |
+
if item.get("type") == "crack":
|
280 |
+
last_detected_cracks.append(captured_frame_path)
|
281 |
+
if len(last_detected_cracks) > 100:
|
282 |
+
last_detected_cracks.pop(0)
|
283 |
+
elif item.get("type") == "hole":
|
284 |
+
last_detected_holes.append(captured_frame_path)
|
285 |
+
if len(last_detected_holes) > 100:
|
286 |
+
last_detected_holes.pop(0)
|
287 |
except Exception as e:
|
288 |
log_entries.append(f"Error saving captured frame: {str(e)}")
|
289 |
logging.error(f"Error saving captured frame: {str(e)}")
|
290 |
|
291 |
# Prepare data for Salesforce dispatch
|
292 |
all_detections = {
|
293 |
+
"detections": all_detected_items,
|
294 |
"metrics": metrics,
|
295 |
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
296 |
"frame_count": frame_count,
|
297 |
+
"gps_coordinates": gps_coord,
|
298 |
+
"shadow_issue": shadow_issue,
|
299 |
+
"thermal": thermal_flag
|
300 |
}
|
301 |
|
302 |
# Dispatch to Salesforce
|
303 |
try:
|
304 |
+
send_to_salesforce(all_detections)
|
305 |
except Exception as e:
|
306 |
log_entries.append(f"Salesforce Dispatch Error: {str(e)}")
|
307 |
logging.error(f"Salesforce dispatch error: {str(e)}")
|
308 |
|
309 |
+
# Save processed frame
|
310 |
try:
|
311 |
frame_path = os.path.join(OUTPUT_DIR, f"frame_{frame_count:04d}.jpg")
|
312 |
cv2.imwrite(frame_path, frame)
|
|
|
318 |
frame_count += 1
|
319 |
last_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
320 |
last_frame = frame.copy()
|
321 |
+
last_metrics = metrics
|
322 |
|
323 |
+
# Track cracks/holes for metrics
|
324 |
+
crack_detected = len([item for item in all_detected_items if item.get("type") == "crack"])
|
325 |
+
hole_detected = len([item for item in all_detected_items if item.get("type") == "hole"])
|
326 |
+
if active_service in ["operations_maintenance", "road_safety"]:
|
327 |
crack_severity_all.extend([
|
328 |
item["severity"]
|
329 |
for item in all_detected_items
|
330 |
+
if item.get("type") in ["crack", "hole"] and "severity" in item
|
331 |
])
|
332 |
+
crack_counts.append(crack_detected + hole_detected)
|
|
|
|
|
|
|
|
|
|
|
333 |
|
334 |
# Log frame processing details
|
335 |
+
log_message = f"{last_timestamp} - Frame {frame_count} - Cracks: {crack_detected} - Holes: {hole_detected} - GPS: {gps_coord}"
|
|
|
|
|
336 |
log_entries.append(log_message)
|
337 |
logging.info(log_message)
|
|
|
338 |
|
339 |
# Limit the size of logs and crack data
|
340 |
if len(log_entries) > 100:
|
|
|
344 |
if len(crack_severity_all) > 500:
|
345 |
crack_severity_all.pop(0)
|
346 |
|
347 |
+
# Resize frame and add metadata for display
|
348 |
+
frame = cv2.resize(last_frame, (640, 480))
|
349 |
+
cv2.putText(frame, f"Frame: {frame_count}", (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
350 |
+
cv2.putText(frame, f"{last_timestamp}", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
351 |
|
352 |
+
# Generate map
|
353 |
+
map_path = generate_map(gps_coordinates[-5:], [item for item in last_metrics.get("items", []) if item.get("type") in ["crack", "hole"]])
|
|
|
|
|
|
|
|
|
|
|
|
|
354 |
|
355 |
return (
|
356 |
frame[:, :, ::-1], # Convert BGR to RGB for Gradio
|
357 |
+
json.dumps(last_metrics, indent=2),
|
358 |
"\n".join(log_entries[-10:]),
|
359 |
+
last_detected_cracks,
|
360 |
+
last_detected_holes,
|
361 |
+
generate_line_chart(),
|
362 |
+
map_path
|
363 |
)
|
364 |
|
365 |
# Gradio UI setup
|
|
|
400 |
|
401 |
with gr.Row():
|
402 |
with gr.Column(scale=3):
|
403 |
+
video_output = gr.Image(label="Live Drone Feed", width=640, height=480, elem_id="live-feed")
|
404 |
with gr.Column(scale=1):
|
405 |
+
metrics_output = gr.Textbox(
|
406 |
label="Detection Metrics",
|
407 |
lines=10,
|
408 |
interactive=False,
|
409 |
+
placeholder="Detection metrics, crack/hole counts will appear here."
|
410 |
)
|
411 |
|
412 |
with gr.Row():
|
413 |
with gr.Column(scale=2):
|
414 |
logs_output = gr.Textbox(label="Live Logs", lines=8, interactive=False)
|
415 |
with gr.Column(scale=1):
|
416 |
+
crack_images = gr.Gallery(label="Detected Cracks (Last 100+)", columns=4, rows=13, height="auto")
|
417 |
+
hole_images = gr.Gallery(label="Detected Holes (Last 100+)", columns=4, rows=13, height="auto")
|
|
|
418 |
|
419 |
with gr.Row():
|
420 |
+
chart_output = gr.Image(label="Crack/Hole Trend")
|
421 |
+
map_output = gr.Image(label="Crack/Hole Locations Map")
|
|
|
|
|
|
|
|
|
|
|
|
|
422 |
|
423 |
with gr.Row():
|
424 |
pause_btn = gr.Button("⏸️ Pause", variant="secondary")
|
425 |
resume_btn = gr.Button("▶️ Resume", variant="primary")
|
426 |
+
frame_slider = gr.Slider(0.05, 1.0, value=0.3, label="Frame Interval (seconds)", step=0.05)
|
427 |
|
428 |
gr.HTML("""
|
429 |
<style>
|
430 |
#live-feed {
|
431 |
border: 2px solid #4682B4;
|
432 |
border-radius: 10px;
|
|
|
433 |
}
|
434 |
.gr-button-primary {
|
435 |
background-color: #4682B4 !important;
|
|
|
|
|
|
|
|
|
|
|
436 |
}
|
437 |
.gr-button-secondary {
|
438 |
background-color: #FF6347 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
}
|
440 |
</style>
|
441 |
""")
|
|
|
486 |
def streaming_loop():
|
487 |
while True:
|
488 |
if not video_loaded:
|
489 |
+
yield None, json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2), "\n".join(log_entries[-10:]), last_detected_cracks, last_detected_holes, None, None
|
490 |
else:
|
491 |
+
frame, metrics, logs, cracks, holes, chart, map_path = monitor_feed()
|
492 |
if frame is None:
|
493 |
+
yield None, metrics, logs, cracks, holes, chart, map_path
|
494 |
else:
|
495 |
+
yield frame, metrics, logs, cracks, holes, chart, map_path
|
496 |
time.sleep(frame_rate)
|
497 |
|
498 |
+
app.load(streaming_loop, outputs=[video_output, metrics_output, logs_output, crack_images, hole_images, chart_output, map_output])
|
499 |
|
500 |
if __name__ == "__main__":
|
501 |
app.launch(share=False)
|