Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -15,6 +15,7 @@ import ultralytics
|
|
| 15 |
import time
|
| 16 |
import piexif
|
| 17 |
import zipfile
|
|
|
|
| 18 |
|
| 19 |
# Set YOLO config directory
|
| 20 |
os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
|
|
@@ -56,13 +57,58 @@ print(f"Ultralytics version: {ultralytics.__version__}")
|
|
| 56 |
print(f"CUDA available: {torch.cuda.is_available()}")
|
| 57 |
|
| 58 |
# Load custom YOLO model
|
| 59 |
-
device = "
|
| 60 |
print(f"Using device: {device}")
|
| 61 |
-
model = YOLO('./data/best.pt').to(device)
|
| 62 |
-
if device == "
|
| 63 |
-
model.
|
| 64 |
print(f"Model classes: {model.names}")
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def zip_directory(folder_path: str, zip_path: str) -> str:
|
| 67 |
"""Zip all files in a directory."""
|
| 68 |
try:
|
|
@@ -90,57 +136,6 @@ def generate_map(gps_coords: List[List[float]], items: List[Dict[str, Any]]) ->
|
|
| 90 |
plt.close()
|
| 91 |
return map_path
|
| 92 |
|
| 93 |
-
def write_geotag(image_path: str, gps_coord: List[float]) -> bool:
|
| 94 |
-
try:
|
| 95 |
-
lat = abs(gps_coord[0])
|
| 96 |
-
lon = abs(gps_coord[1])
|
| 97 |
-
lat_ref = "N" if gps_coord[0] >= 0 else "S"
|
| 98 |
-
lon_ref = "E" if gps_coord[1] >= 0 else "W"
|
| 99 |
-
exif_dict = piexif.load(image_path) if os.path.exists(image_path) else {"GPS": {}}
|
| 100 |
-
exif_dict["GPS"] = {
|
| 101 |
-
piexif.GPSIFD.GPSLatitudeRef: lat_ref,
|
| 102 |
-
piexif.GPSIFD.GPSLatitude: ((int(lat), 1), (0, 1), (0, 1)),
|
| 103 |
-
piexif.GPSIFD.GPSLongitudeRef: lon_ref,
|
| 104 |
-
piexif.GPSIFD.GPSLongitude: ((int(lon), 1), (0, 1), (0, 1))
|
| 105 |
-
}
|
| 106 |
-
piexif.insert(piexif.dump(exif_dict), image_path)
|
| 107 |
-
return True
|
| 108 |
-
except Exception as e:
|
| 109 |
-
logging.error(f"Failed to geotag {image_path}: {str(e)}")
|
| 110 |
-
log_entries.append(f"Error: Failed to geotag {image_path}: {str(e)}")
|
| 111 |
-
return False
|
| 112 |
-
|
| 113 |
-
def write_flight_log(frame_count: int, gps_coord: List[float], timestamp: str) -> str:
|
| 114 |
-
log_path = os.path.join(FLIGHT_LOG_DIR, f"flight_log_{frame_count:06d}.csv")
|
| 115 |
-
try:
|
| 116 |
-
with open(log_path, 'w', newline='') as csvfile:
|
| 117 |
-
writer = csv.writer(csvfile)
|
| 118 |
-
writer.writerow(["Frame", "Timestamp", "Latitude", "Longitude", "Speed_ms", "Satellites", "Altitude_m"])
|
| 119 |
-
writer.writerow([frame_count, timestamp, gps_coord[0], gps_coord[1], 5.0, 12, 60])
|
| 120 |
-
return log_path
|
| 121 |
-
except Exception as e:
|
| 122 |
-
logging.error(f"Failed to write flight log {log_path}: {str(e)}")
|
| 123 |
-
log_entries.append(f"Error: Failed to write flight log {log_path}: {str(e)}")
|
| 124 |
-
return ""
|
| 125 |
-
|
| 126 |
-
def check_image_quality(frame: np.ndarray, input_resolution: int) -> bool:
|
| 127 |
-
height, width, _ = frame.shape
|
| 128 |
-
frame_resolution = width * height
|
| 129 |
-
if frame_resolution < 12_000_000:
|
| 130 |
-
log_entries.append(f"Frame {frame_count}: Resolution {width}x{height} ({frame_resolution/1e6:.2f}MP) below 12MP, non-compliant")
|
| 131 |
-
if frame_resolution < input_resolution:
|
| 132 |
-
log_entries.append(f"Frame {frame_count}: Output resolution {width}x{height} below input resolution")
|
| 133 |
-
return False
|
| 134 |
-
return True
|
| 135 |
-
|
| 136 |
-
def update_metrics(detections: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 137 |
-
counts = Counter([det["label"] for det in detections])
|
| 138 |
-
return {
|
| 139 |
-
"items": [{"type": k, "count": v} for k, v in counts.items()],
|
| 140 |
-
"total_detections": len(detections),
|
| 141 |
-
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
def generate_line_chart() -> Optional[str]:
|
| 145 |
if not detected_counts:
|
| 146 |
return None
|
|
@@ -361,6 +356,9 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
|
|
| 361 |
images_zip = zip_directory(CAPTURED_FRAMES_DIR, os.path.join(OUTPUT_DIR, "captured_frames.zip"))
|
| 362 |
logs_zip = zip_directory(FLIGHT_LOG_DIR, os.path.join(OUTPUT_DIR, "flight_logs.zip"))
|
| 363 |
|
|
|
|
|
|
|
|
|
|
| 364 |
return (
|
| 365 |
output_path,
|
| 366 |
json.dumps(last_metrics, indent=2),
|
|
@@ -368,56 +366,8 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
|
|
| 368 |
detected_issues,
|
| 369 |
chart_path,
|
| 370 |
map_path,
|
| 371 |
-
|
| 372 |
images_zip,
|
| 373 |
logs_zip,
|
| 374 |
output_path # For video download
|
| 375 |
)
|
| 376 |
-
|
| 377 |
-
# Gradio interface
|
| 378 |
-
with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
|
| 379 |
-
gr.Markdown("# NHAI Road Defect Detection Dashboard")
|
| 380 |
-
with gr.Row():
|
| 381 |
-
with gr.Column(scale=3):
|
| 382 |
-
video_input = gr.Video(label="Upload Video (12MP recommended for NHAI compliance)")
|
| 383 |
-
width_slider = gr.Slider(320, 4000, value=4000, label="Output Width", step=1)
|
| 384 |
-
height_slider = gr.Slider(240, 3000, value=3000, label="Output Height", step=1)
|
| 385 |
-
skip_slider = gr.Slider(1, 10, value=5, label="Frame Skip", step=1)
|
| 386 |
-
process_btn = gr.Button("Process Video", variant="primary")
|
| 387 |
-
with gr.Column(scale=1):
|
| 388 |
-
metrics_output = gr.Textbox(label="Detection Metrics", lines=5, interactive=False)
|
| 389 |
-
with gr.Row():
|
| 390 |
-
video_output = gr.Video(label="Processed Video")
|
| 391 |
-
issue_gallery = gr.Gallery(label="Detected Issues", columns=4, height="auto", object_fit="contain")
|
| 392 |
-
with gr.Row():
|
| 393 |
-
chart_output = gr.Image(label="Detection Trend")
|
| 394 |
-
map_output = gr.Image(label="Issue Locations Map")
|
| 395 |
-
with gr.Row():
|
| 396 |
-
logs_output = gr.Textbox(label="Logs", lines=5, interactive=False)
|
| 397 |
-
with gr.Row():
|
| 398 |
-
gr.Markdown("## Download Results")
|
| 399 |
-
with gr.Row():
|
| 400 |
-
json_download = gr.File(label="Download Data Lake JSON")
|
| 401 |
-
images_zip_download = gr.File(label="Download Geotagged Images (ZIP)")
|
| 402 |
-
logs_zip_download = gr.File(label="Download Flight Logs (ZIP)")
|
| 403 |
-
video_download = gr.File(label="Download Processed Video")
|
| 404 |
-
|
| 405 |
-
process_btn.click(
|
| 406 |
-
fn=process_video,
|
| 407 |
-
inputs=[video_input, width_slider, height_slider, skip_slider],
|
| 408 |
-
outputs=[
|
| 409 |
-
video_output,
|
| 410 |
-
metrics_output,
|
| 411 |
-
logs_output,
|
| 412 |
-
issue_gallery,
|
| 413 |
-
chart_output,
|
| 414 |
-
map_output,
|
| 415 |
-
json_download,
|
| 416 |
-
images_zip_download,
|
| 417 |
-
logs_zip_download,
|
| 418 |
-
video_download
|
| 419 |
-
]
|
| 420 |
-
)
|
| 421 |
-
|
| 422 |
-
if __name__ == "__main__":
|
| 423 |
-
iface.launch()
|
|
|
|
| 15 |
import time
|
| 16 |
import piexif
|
| 17 |
import zipfile
|
| 18 |
+
from fpdf import FPDF # PDF generation
|
| 19 |
|
| 20 |
# Set YOLO config directory
|
| 21 |
os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
|
|
|
|
| 57 |
print(f"CUDA available: {torch.cuda.is_available()}")
|
| 58 |
|
| 59 |
# Load custom YOLO model
|
| 60 |
+
device = "cpu" # Force the use of CPU
|
| 61 |
print(f"Using device: {device}")
|
| 62 |
+
model = YOLO('./data/best.pt').to(device) # Model is now moved to the CPU
|
| 63 |
+
if device == "cpu": # Optional check, not strictly needed but can help to ensure
|
| 64 |
+
model.float() # Ensure model is running on the CPU
|
| 65 |
print(f"Model classes: {model.names}")
|
| 66 |
|
| 67 |
+
# Generate PDF report
|
| 68 |
+
def generate_pdf_report(log_entries, detected_issues, chart_path, map_path, metrics):
|
| 69 |
+
pdf = FPDF()
|
| 70 |
+
pdf.set_auto_page_break(auto=True, margin=15)
|
| 71 |
+
pdf.add_page()
|
| 72 |
+
|
| 73 |
+
# Add Title
|
| 74 |
+
pdf.set_font("Arial", "B", 16)
|
| 75 |
+
pdf.cell(200, 10, txt="Road Defect Detection Report", ln=True, align="C")
|
| 76 |
+
pdf.ln(10)
|
| 77 |
+
|
| 78 |
+
# Add Log Entries
|
| 79 |
+
pdf.set_font("Arial", size=12)
|
| 80 |
+
pdf.cell(200, 10, txt="Log Entries:", ln=True)
|
| 81 |
+
pdf.multi_cell(0, 10, txt="\n".join(log_entries))
|
| 82 |
+
pdf.ln(5)
|
| 83 |
+
|
| 84 |
+
# Add Detected Issues
|
| 85 |
+
pdf.cell(200, 10, txt="Detected Issues:", ln=True)
|
| 86 |
+
for issue in detected_issues:
|
| 87 |
+
pdf.cell(200, 10, txt=issue, ln=True)
|
| 88 |
+
pdf.ln(5)
|
| 89 |
+
|
| 90 |
+
# Add Metrics
|
| 91 |
+
pdf.cell(200, 10, txt="Detection Metrics:", ln=True)
|
| 92 |
+
pdf.multi_cell(0, 10, txt=json.dumps(metrics, indent=2))
|
| 93 |
+
pdf.ln(5)
|
| 94 |
+
|
| 95 |
+
# Add Chart Image
|
| 96 |
+
if chart_path:
|
| 97 |
+
pdf.cell(200, 10, txt="Detection Trend Chart:", ln=True)
|
| 98 |
+
pdf.image(chart_path, x=10, y=pdf.get_y(), w=180)
|
| 99 |
+
pdf.ln(80)
|
| 100 |
+
|
| 101 |
+
# Add Map Image
|
| 102 |
+
if map_path:
|
| 103 |
+
pdf.cell(200, 10, txt="Issue Locations Map:", ln=True)
|
| 104 |
+
pdf.image(map_path, x=10, y=pdf.get_y(), w=180)
|
| 105 |
+
pdf.ln(80)
|
| 106 |
+
|
| 107 |
+
# Save PDF
|
| 108 |
+
pdf_output_path = os.path.join(OUTPUT_DIR, "detection_report.pdf")
|
| 109 |
+
pdf.output(pdf_output_path)
|
| 110 |
+
return pdf_output_path
|
| 111 |
+
|
| 112 |
def zip_directory(folder_path: str, zip_path: str) -> str:
|
| 113 |
"""Zip all files in a directory."""
|
| 114 |
try:
|
|
|
|
| 136 |
plt.close()
|
| 137 |
return map_path
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
def generate_line_chart() -> Optional[str]:
|
| 140 |
if not detected_counts:
|
| 141 |
return None
|
|
|
|
| 356 |
images_zip = zip_directory(CAPTURED_FRAMES_DIR, os.path.join(OUTPUT_DIR, "captured_frames.zip"))
|
| 357 |
logs_zip = zip_directory(FLIGHT_LOG_DIR, os.path.join(OUTPUT_DIR, "flight_logs.zip"))
|
| 358 |
|
| 359 |
+
# Generate PDF report
|
| 360 |
+
pdf_path = generate_pdf_report(log_entries, detected_issues, chart_path, map_path, last_metrics)
|
| 361 |
+
|
| 362 |
return (
|
| 363 |
output_path,
|
| 364 |
json.dumps(last_metrics, indent=2),
|
|
|
|
| 366 |
detected_issues,
|
| 367 |
chart_path,
|
| 368 |
map_path,
|
| 369 |
+
pdf_path, # PDF report path
|
| 370 |
images_zip,
|
| 371 |
logs_zip,
|
| 372 |
output_path # For video download
|
| 373 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|