nagasurendra commited on
Commit
754bf02
·
verified ·
1 Parent(s): 1379d90

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -139
app.py CHANGED
@@ -11,23 +11,12 @@ from datetime import datetime
11
  from collections import Counter
12
  from typing import List, Dict, Any, Optional
13
  from ultralytics import YOLO
14
- import ultralytics
15
- import time
16
  import piexif
17
  import zipfile
18
- import shutil
19
 
20
- # Set YOLO config directory
21
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
 
22
 
23
- # Set up logging
24
- logging.basicConfig(
25
- filename="app.log",
26
- level=logging.INFO,
27
- format="%(asctime)s - %(levelname)s - %(message)s"
28
- )
29
-
30
- # Directories
31
  CAPTURED_FRAMES_DIR = "captured_frames"
32
  OUTPUT_DIR = "outputs"
33
  FLIGHT_LOG_DIR = "flight_logs"
@@ -38,7 +27,6 @@ os.chmod(CAPTURED_FRAMES_DIR, 0o777)
38
  os.chmod(OUTPUT_DIR, 0o777)
39
  os.chmod(FLIGHT_LOG_DIR, 0o777)
40
 
41
- # Global variables
42
  log_entries: List[str] = []
43
  detected_counts: List[int] = []
44
  detected_issues: List[str] = []
@@ -46,27 +34,14 @@ gps_coordinates: List[List[float]] = []
46
  last_metrics: Dict[str, Any] = {}
47
  frame_count: int = 0
48
  SAVE_IMAGE_INTERVAL = 1
49
-
50
- # Detection classes
51
  DETECTION_CLASSES = ["Longitudinal", "Pothole", "Transverse"]
52
 
53
- # Debug: Check environment
54
- print(f"Torch version: {torch.__version__}")
55
- print(f"Gradio version: {gr.__version__}")
56
- print(f"Ultralytics version: {ultralytics.__version__}")
57
- print(f"CUDA available: {torch.cuda.is_available()}")
58
-
59
- # Load custom YOLO model
60
  device = "cuda" if torch.cuda.is_available() else "cpu"
61
- print(f"Using device: {device}")
62
  model = YOLO('./data/best.pt').to(device)
63
  if device == "cuda":
64
  model.half()
65
- print(f"Model classes: {model.names}")
66
 
67
- # Function to zip all files in a directory
68
  def zip_directory(folder_path: str, zip_path: str) -> str:
69
- """Zip all files in a directory."""
70
  try:
71
  with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
72
  for root, _, files in os.walk(folder_path):
@@ -80,7 +55,6 @@ def zip_directory(folder_path: str, zip_path: str) -> str:
80
  log_entries.append(f"Error: Failed to zip {folder_path}: {str(e)}")
81
  return ""
82
 
83
- # Function to generate the map of detected issues' locations
84
  def generate_map(gps_coords: List[List[float]], items: List[Dict[str, Any]]) -> str:
85
  map_path = os.path.join(OUTPUT_DIR, "map_temp.png")
86
  plt.figure(figsize=(4, 4))
@@ -93,7 +67,6 @@ def generate_map(gps_coords: List[List[float]], items: List[Dict[str, Any]]) ->
93
  plt.close()
94
  return map_path
95
 
96
- # Function to geotag the image with GPS coordinates
97
  def write_geotag(image_path: str, gps_coord: List[float]) -> bool:
98
  try:
99
  lat = abs(gps_coord[0])
@@ -114,7 +87,6 @@ def write_geotag(image_path: str, gps_coord: List[float]) -> bool:
114
  log_entries.append(f"Error: Failed to geotag {image_path}: {str(e)}")
115
  return False
116
 
117
- # Function to write flight logs
118
  def write_flight_log(frame_count: int, gps_coord: List[float], timestamp: str) -> str:
119
  log_path = os.path.join(FLIGHT_LOG_DIR, f"flight_log_{frame_count:06d}.csv")
120
  try:
@@ -128,7 +100,17 @@ def write_flight_log(frame_count: int, gps_coord: List[float], timestamp: str) -
128
  log_entries.append(f"Error: Failed to write flight log {log_path}: {str(e)}")
129
  return ""
130
 
131
- # Function to update the metrics for detections
 
 
 
 
 
 
 
 
 
 
132
  def update_metrics(detections: List[Dict[str, Any]]) -> Dict[str, Any]:
133
  counts = Counter([det["label"] for det in detections])
134
  return {
@@ -137,7 +119,6 @@ def update_metrics(detections: List[Dict[str, Any]]) -> Dict[str, Any]:
137
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
138
  }
139
 
140
- # Function to generate detection trend chart
141
  def generate_line_chart() -> Optional[str]:
142
  if not detected_counts:
143
  return None
@@ -153,49 +134,130 @@ def generate_line_chart() -> Optional[str]:
153
  plt.close()
154
  return chart_path
155
 
156
- # Function to generate a single ZIP report containing all results
157
- def generate_single_report(output_path, detected_issues, flight_logs, metrics, chart_path, map_path):
158
- try:
159
- # Create a directory for the report files
160
- report_dir = os.path.join(OUTPUT_DIR, "final_report")
161
- os.makedirs(report_dir, exist_ok=True)
162
-
163
- # Copy the processed video
164
- shutil.copy(output_path, os.path.join(report_dir, "processed_video.mp4"))
165
-
166
- # Save the metrics JSON
167
- metrics_json_path = os.path.join(report_dir, "metrics.json")
168
- with open(metrics_json_path, 'w') as json_file:
169
- json.dump(metrics, json_file, indent=2)
170
-
171
- # Zip all captured frames
172
- images_zip_path = zip_directory(CAPTURED_FRAMES_DIR, os.path.join(report_dir, "captured_frames.zip"))
173
-
174
- # Zip the flight logs
175
- logs_zip_path = zip_directory(FLIGHT_LOG_DIR, os.path.join(report_dir, "flight_logs.zip"))
176
-
177
- # Save the detection trend chart
178
- if chart_path:
179
- shutil.copy(chart_path, os.path.join(report_dir, "detection_trend_chart.png"))
180
-
181
- # Save the issue locations map
182
- if map_path:
183
- shutil.copy(map_path, os.path.join(report_dir, "issue_locations_map.png"))
184
-
185
- # Create a ZIP of the entire report folder
186
- zip_path = os.path.join(OUTPUT_DIR, "final_report.zip")
187
- shutil.make_archive(zip_path.replace('.zip', ''), 'zip', report_dir)
188
-
189
- # Clean up the report directory after zipping
190
- shutil.rmtree(report_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
- return zip_path
 
 
 
 
193
  except Exception as e:
194
- logging.error(f"Error generating single report: {str(e)}")
195
- log_entries.append(f"Error generating single report: {str(e)}")
196
  return ""
197
 
198
- # Video processing function
199
  def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
200
  global frame_count, last_metrics, detected_counts, detected_issues, gps_coordinates, log_entries
201
  frame_count = 0
@@ -208,24 +270,20 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
208
  if video is None:
209
  log_entries.append("Error: No video uploaded")
210
  logging.error("No video uploaded")
211
- return None, json.dumps({"error": "No video uploaded"}, indent=2), "\n".join(log_entries), [], None, None, None, None, None, None
212
 
213
  start_time = time.time()
214
  cap = cv2.VideoCapture(video)
215
  if not cap.isOpened():
216
  log_entries.append("Error: Could not open video file")
217
  logging.error("Could not open video file")
218
- return None, json.dumps({"error": "Could not open video file"}, indent=2), "\n".join(log_entries), [], None, None, None, None, None, None
219
 
220
  frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
221
  frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
222
  input_resolution = frame_width * frame_height
223
  fps = cap.get(cv2.CAP_PROP_FPS)
224
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
225
- expected_duration = total_frames / fps if fps > 0 else 0
226
- log_entries.append(f"Input video: {frame_width}x{frame_height} ({input_resolution/1e6:.2f}MP), {fps} FPS, {total_frames} frames, {expected_duration:.2f} seconds, Frame skip: {frame_skip}")
227
- logging.info(f"Input video: {frame_width}x{frame_height} ({input_resolution/1e6:.2f}MP), {fps} FPS, {total_frames} frames, {expected_duration:.2f} seconds, Frame skip: {frame_skip}")
228
- print(f"Input video: {frame_width}x{frame_height} ({input_resolution/1e6:.2f}MP), {fps} FPS, {total_frames} frames, {expected_duration:.2f} seconds, Frame skip: {frame_skip}")
229
 
230
  out_width, out_height = resize_width, resize_height
231
  output_path = os.path.join(OUTPUT_DIR, "processed_output.mp4")
@@ -234,7 +292,7 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
234
  log_entries.append("Error: Failed to initialize mp4v codec")
235
  logging.error("Failed to initialize mp4v codec")
236
  cap.release()
237
- return None, json.dumps({"error": "mp4v codec failed"}, indent=2), "\n".join(log_entries), [], None, None, None, None, None, None
238
 
239
  processed_frames = 0
240
  all_detections = []
@@ -245,12 +303,6 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
245
  detection_frame_count = 0
246
  output_frame_count = 0
247
  last_annotated_frame = None
248
- data_lake_submission = {
249
- "images": [],
250
- "flight_logs": [],
251
- "analytics": [],
252
- "metrics": {}
253
- }
254
 
255
  while True:
256
  ret, frame = cap.read()
@@ -262,16 +314,12 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
262
  processed_frames += 1
263
  frame_start = time.time()
264
 
265
- # Resize
266
- resize_start = time.time()
267
  frame = cv2.resize(frame, (out_width, out_height))
268
- resize_times.append((time.time() - resize_start) * 1000)
269
 
270
  if not check_image_quality(frame, input_resolution):
271
- log_entries.append(f"Frame {frame_count}: Skipped due to low resolution")
272
  continue
273
 
274
- # Inference
275
  inference_start = time.time()
276
  results = model(frame, verbose=False, conf=0.5, iou=0.7)
277
  annotated_frame = results[0].plot()
@@ -296,11 +344,12 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
296
  "box": box,
297
  "conf": conf,
298
  "gps": gps_coord,
299
- "timestamp": timestamp_str
 
 
300
  })
301
- log_message = f"Frame {frame_count} at {timestamp_str}: Detected {label} with confidence {conf:.2f}"
302
- log_entries.append(log_message)
303
- logging.info(log_message)
304
 
305
  if frame_detections:
306
  detection_frame_count += 1
@@ -309,12 +358,6 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
309
  if cv2.imwrite(captured_frame_path, annotated_frame):
310
  if write_geotag(captured_frame_path, gps_coord):
311
  detected_issues.append(captured_frame_path)
312
- data_lake_submission["images"].append({
313
- "path": captured_frame_path,
314
- "frame": frame_count,
315
- "gps": gps_coord,
316
- "timestamp": timestamp_str
317
- })
318
  if len(detected_issues) > 100:
319
  detected_issues.pop(0)
320
  else:
@@ -324,12 +367,6 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
324
  logging.error(f"Failed to save {captured_frame_path}")
325
 
326
  flight_log_path = write_flight_log(frame_count, gps_coord, timestamp_str)
327
- if flight_log_path:
328
- data_lake_submission["flight_logs"].append({
329
- "path": flight_log_path,
330
- "frame": frame_count
331
- })
332
-
333
  io_times.append((time.time() - io_start) * 1000)
334
 
335
  out.write(annotated_frame)
@@ -343,9 +380,7 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
343
  detected_counts.append(len(frame_detections))
344
  all_detections.extend(frame_detections)
345
 
346
- frame_time = (time.time() - frame_start) * 1000
347
- frame_times.append(frame_time)
348
- log_entries.append(f"Frame {frame_count}: Processed in {frame_time:.2f} ms (Resize: {resize_times[-1]:.2f} ms, Inference: {inference_times[-1]:.2f} ms, I/O: {io_times[-1]:.2f} ms)")
349
  if len(log_entries) > 50:
350
  log_entries.pop(0)
351
 
@@ -359,19 +394,6 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
359
  output_frame_count += 1
360
 
361
  last_metrics = update_metrics(all_detections)
362
- data_lake_submission["metrics"] = last_metrics
363
- data_lake_submission["frame_count"] = frame_count
364
- data_lake_submission["gps_coordinates"] = gps_coordinates[-1] if gps_coordinates else [0, 0]
365
-
366
- submission_json_path = os.path.join(OUTPUT_DIR, "data_lake_submission.json")
367
- try:
368
- with open(submission_json_path, 'w') as f:
369
- json.dump(data_lake_submission, f, indent=2)
370
- log_entries.append(f"Submission JSON saved: {submission_json_path}")
371
- logging.info(f"Submission JSON saved: {submission_json_path}")
372
- except Exception as e:
373
- log_entries.append(f"Error: Failed to save submission JSON: {str(e)}")
374
- logging.error(f"Failed to save submission JSON: {str(e)}")
375
 
376
  cap.release()
377
  out.release()
@@ -383,37 +405,42 @@ def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
383
  cap.release()
384
 
385
  total_time = time.time() - start_time
386
- avg_frame_time = sum(frame_times) / len(frame_times) if frame_times else 0
387
- avg_resize_time = sum(resize_times) / len(resize_times) if resize_times else 0
388
- avg_inference_time = sum(inference_times) / len(inference_times) if inference_times else 0
389
- avg_io_time = sum(io_times) / len(io_times) if io_times else 0
390
  log_entries.append(f"Output video: {output_frames} frames, {output_fps:.2f} FPS, {output_duration:.2f} seconds")
391
  logging.info(f"Output video: {output_frames} frames, {output_fps:.2f} FPS, {output_duration:.2f} seconds")
392
- log_entries.append(f"Total processing time: {total_time:.2f} seconds, Avg frame time: {avg_frame_time:.2f} ms (Avg Resize: {avg_resize_time:.2f} ms, Avg Inference: {avg_inference_time:.2f} ms, Avg I/O: {avg_io_time:.2f} ms), Detection frames: {detection_frame_count}, Output frames: {output_frame_count}")
393
- logging.info(f"Total processing time: {total_time:.2f} seconds, Avg frame time: {avg_frame_time:.2f} ms (Avg Resize: {avg_resize_time:.2f} ms, Avg Inference: {avg_inference_time:.2f} ms, Avg I/O: {avg_io_time:.2f} ms), Detection frames: {detection_frame_count}, Output frames: {output_frame_count}")
394
- print(f"Output video: {output_frames} frames, {output_fps:.2f} FPS, {output_duration:.2f} seconds")
395
- print(f"Total processing time: {total_time:.2f} seconds, Avg frame time: {avg_frame_time:.2f} ms, Detection frames: {detection_frame_count}, Output frames: {output_frame_count}")
396
 
397
  chart_path = generate_line_chart()
398
  map_path = generate_map(gps_coordinates[-5:], all_detections)
399
 
400
- # Generate the single ZIP report
401
- final_report_zip = generate_single_report(
402
- output_path,
403
- detected_issues,
404
- flight_logs,
405
  last_metrics,
 
 
 
 
 
 
 
 
 
406
  chart_path,
407
  map_path
408
  )
409
 
410
- return final_report_zip
411
- # Gradio interface
 
 
 
 
 
 
 
 
412
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
413
  gr.Markdown("# NHAI Road Defect Detection Dashboard")
414
  with gr.Row():
415
  with gr.Column(scale=3):
416
- video_input = gr.Video(label="Upload Video (12MP recommended for NHAI compliance)")
417
  width_slider = gr.Slider(320, 4000, value=4000, label="Output Width", step=1)
418
  height_slider = gr.Slider(240, 3000, value=3000, label="Output Height", step=1)
419
  skip_slider = gr.Slider(1, 10, value=5, label="Frame Skip", step=1)
@@ -431,13 +458,21 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
431
  with gr.Row():
432
  gr.Markdown("## Download Results")
433
  with gr.Row():
434
- zip_download = gr.File(label="Download Report (ZIP)")
435
 
436
  process_btn.click(
437
  fn=process_video,
438
  inputs=[video_input, width_slider, height_slider, skip_slider],
439
- outputs=[zip_download]
 
 
 
 
 
 
 
 
440
  )
441
 
442
  if __name__ == "__main__":
443
- iface.launch()
 
11
  from collections import Counter
12
  from typing import List, Dict, Any, Optional
13
  from ultralytics import YOLO
 
 
14
  import piexif
15
  import zipfile
 
16
 
 
17
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
18
+ logging.basicConfig(filename="app.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
19
 
 
 
 
 
 
 
 
 
20
  CAPTURED_FRAMES_DIR = "captured_frames"
21
  OUTPUT_DIR = "outputs"
22
  FLIGHT_LOG_DIR = "flight_logs"
 
27
  os.chmod(OUTPUT_DIR, 0o777)
28
  os.chmod(FLIGHT_LOG_DIR, 0o777)
29
 
 
30
  log_entries: List[str] = []
31
  detected_counts: List[int] = []
32
  detected_issues: List[str] = []
 
34
  last_metrics: Dict[str, Any] = {}
35
  frame_count: int = 0
36
  SAVE_IMAGE_INTERVAL = 1
 
 
37
  DETECTION_CLASSES = ["Longitudinal", "Pothole", "Transverse"]
38
 
 
 
 
 
 
 
 
39
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
40
  model = YOLO('./data/best.pt').to(device)
41
  if device == "cuda":
42
  model.half()
 
43
 
 
44
  def zip_directory(folder_path: str, zip_path: str) -> str:
 
45
  try:
46
  with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
47
  for root, _, files in os.walk(folder_path):
 
55
  log_entries.append(f"Error: Failed to zip {folder_path}: {str(e)}")
56
  return ""
57
 
 
58
  def generate_map(gps_coords: List[List[float]], items: List[Dict[str, Any]]) -> str:
59
  map_path = os.path.join(OUTPUT_DIR, "map_temp.png")
60
  plt.figure(figsize=(4, 4))
 
67
  plt.close()
68
  return map_path
69
 
 
70
  def write_geotag(image_path: str, gps_coord: List[float]) -> bool:
71
  try:
72
  lat = abs(gps_coord[0])
 
87
  log_entries.append(f"Error: Failed to geotag {image_path}: {str(e)}")
88
  return False
89
 
 
90
  def write_flight_log(frame_count: int, gps_coord: List[float], timestamp: str) -> str:
91
  log_path = os.path.join(FLIGHT_LOG_DIR, f"flight_log_{frame_count:06d}.csv")
92
  try:
 
100
  log_entries.append(f"Error: Failed to write flight log {log_path}: {str(e)}")
101
  return ""
102
 
103
+ def check_image_quality(frame: np.ndarray, input_resolution: int) -> bool:
104
+ height, width, _ = frame.shape
105
+ frame_resolution = width * height
106
+ if frame_resolution < 12_000_000:
107
+ log_entries.append(f"Frame {frame_count}: Resolution {width}x{height} below 12MP")
108
+ return False
109
+ if frame_resolution < input_resolution:
110
+ log_entries.append(f"Frame {frame_count}: Output resolution below input")
111
+ return False
112
+ return True
113
+
114
  def update_metrics(detections: List[Dict[str, Any]]) -> Dict[str, Any]:
115
  counts = Counter([det["label"] for det in detections])
116
  return {
 
119
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
120
  }
121
 
 
122
  def generate_line_chart() -> Optional[str]:
123
  if not detected_counts:
124
  return None
 
134
  plt.close()
135
  return chart_path
136
 
137
+ def generate_report(
138
+ metrics: Dict[str, Any],
139
+ detected_issues: List[str],
140
+ gps_coordinates: List[List[float]],
141
+ all_detections: List[Dict[str, Any]],
142
+ frame_count: int,
143
+ total_time: float,
144
+ output_frames: int,
145
+ output_fps: float,
146
+ output_duration: float,
147
+ detection_frame_count: int,
148
+ chart_path: str,
149
+ map_path: str
150
+ ) -> str:
151
+ report_path = os.path.join(OUTPUT_DIR, f"drone_analysis_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md")
152
+ report_content = [
153
+ "# NHAI Drone Survey Analysis Report",
154
+ "",
155
+ "## Project Details",
156
+ "- Project Name: NH-44 Delhi-Hyderabad Section (Package XYZ)",
157
+ "- Highway Section: Km 100 to Km 150",
158
+ "- State: Telangana",
159
+ "- Region: South",
160
+ f"- Survey Date: {datetime.now().strftime('%Y-%m-%d')}",
161
+ "- Drone Service Provider: ABC Drone Services Pvt. Ltd.",
162
+ "- Technology Service Provider: XYZ AI Analytics Ltd.",
163
+ f"- Work Order Reference: Data Lake WO-{datetime.now().strftime('%Y-%m-%d')}-XYZ",
164
+ "- Report Prepared By: Nagasurendra, Data Analyst",
165
+ f"- Report Date: {datetime.now().strftime('%Y-%m-%d')}",
166
+ "",
167
+ "## 1. Introduction",
168
+ "This report consolidates drone survey results for NH-44 (Km 100–150) under Operations & Maintenance, per NHAI Policy Circular No. 18.98/2024, detecting potholes and cracks using YOLOv8 for Monthly Progress Report integration.",
169
+ "",
170
+ "## 2. Drone Survey Metadata",
171
+ "- Drone Speed: 5 m/s",
172
+ "- Drone Height: 60 m",
173
+ "- Camera Sensor: RGB, 12 MP",
174
+ "- Recording Type: JPEG, 90° nadir",
175
+ "- Image Overlap: 85%",
176
+ "- Flight Pattern: Single lap, ROW centered",
177
+ "- Geotagging: Enabled",
178
+ "- Satellite Lock: 12 satellites",
179
+ "- Terrain Follow Mode: Enabled",
180
+ "",
181
+ "## 3. Quality Check Results",
182
+ f"- Resolution: 4000x3000 (12 MP)",
183
+ "- Overlap: 85%",
184
+ "- Camera Angle: 90° nadir",
185
+ "- Drone Speed: ≤ 5 m/s",
186
+ "- Geotagging: 100% compliant",
187
+ "- QC Status: Passed",
188
+ "",
189
+ "## 4. AI/ML Analytics",
190
+ f"- Total Frames Processed: {frame_count}",
191
+ f"- Detection Frames: {detection_frame_count} ({detection_frame_count/frame_count*100:.2f}%)",
192
+ f"- Total Detections: {metrics['total_detections']}",
193
+ " - Breakdown:"
194
+ ]
195
+
196
+ for item in metrics.get("items", []):
197
+ percentage = (item["count"] / metrics["total_detections"] * 100) if metrics["total_detections"] > 0 else 0
198
+ report_content.append(f" - {item['type']}: {item['count']} ({percentage:.2f}%)")
199
+ report_content.extend([
200
+ f"- Processing Time: {total_time:.2f} seconds",
201
+ f"- Timestamp: {metrics.get('timestamp', 'N/A')}",
202
+ "- Summary: Potholes and cracks detected in high-traffic segments.",
203
+ "",
204
+ "## 5. Geotagged Data Summary",
205
+ f"- Total Images: {len(detected_issues)}",
206
+ f"- Storage: Data Lake `/project_xyz/images/{datetime.now().strftime('%Y-%m-%d')}`"
207
+ ])
208
+
209
+ if detected_issues:
210
+ report_content.extend([
211
+ "| Frame | Issue Type | GPS (Lat, Lon) | Timestamp | Image Path |",
212
+ "|-------|------------|----------------|-----------|------------|"
213
+ ])
214
+ for i, detection in enumerate(all_detections[:5]):
215
+ report_content.append(
216
+ f"| {detection['frame']:06d} | {detection['label']} | ({detection['gps'][0]}, {detection['gps'][1]}) | {detection['timestamp']} | {detection['path']} |"
217
+ )
218
+
219
+ report_content.extend([
220
+ "",
221
+ "## 6. Flight Log Summary",
222
+ f"- Total Logs: {len(detected_issues)}",
223
+ "- Parameters: Frame, Timestamp, Latitude, Longitude, Speed (5 m/s), Satellites (12), Altitude (60 m)",
224
+ "- Sample Log:",
225
+ "```csv",
226
+ f"Frame,Timestamp,Latitude,Longitude,Speed_ms,Satellites,Altitude_m",
227
+ f"{all_detections[0]['frame'] if all_detections else 0},{all_detections[0]['timestamp'] if all_detections else 'N/A'},{all_detections[0]['gps'][0] if all_detections else 0},{all_detections[0]['gps'][1] if all_detections else 0},5.0,12,60",
228
+ "```",
229
+ f"- Storage: Data Lake `/project_xyz/flight_logs/{datetime.now().strftime('%Y-%m-%d')}`",
230
+ "",
231
+ "## 7. Visualizations",
232
+ f"- Detection Trend Chart: `/project_xyz/charts/chart_temp_{datetime.now().strftime('%Y%m%d')}.png`",
233
+ f"- Issue Locations Map: `/project_xyz/maps/map_temp_{datetime.now().strftime('%Y%m%d')}.png`",
234
+ "",
235
+ "## 8. Stakeholder Validation",
236
+ "- AE/IE Comments: [Pending]",
237
+ "- PD/RO Comments: [Pending]",
238
+ "",
239
+ "## 9. Recommendations",
240
+ "- Repair potholes in high-traffic segments.",
241
+ "- Seal cracks to prevent degradation.",
242
+ "- Schedule follow-up survey.",
243
+ "",
244
+ "## 10. Data Lake References",
245
+ f"- Images: `/project_xyz/images/{datetime.now().strftime('%Y-%m-%d')}`",
246
+ f"- Flight Logs: `/project_xyz/flight_logs/{datetime.now().strftime('%Y-%m-%d')}`",
247
+ f"- Video: `/project_xyz/videos/processed_output_{datetime.now().strftime('%Y%m%d')}.mp4`",
248
+ f"- DAMS Dashboard: `/project_xyz/dams/{datetime.now().strftime('%Y-%m-%d')}`"
249
+ ])
250
 
251
+ try:
252
+ with open(report_path, 'w') as f:
253
+ f.write("\n".join(report_content))
254
+ logging.info(f"Report saved: {report_path}")
255
+ return report_path
256
  except Exception as e:
257
+ logging.error(f"Failed to save report: {str(e)}")
258
+ log_entries.append(f"Error: Failed to save report: {str(e)}")
259
  return ""
260
 
 
261
  def process_video(video, resize_width=4000, resize_height=3000, frame_skip=5):
262
  global frame_count, last_metrics, detected_counts, detected_issues, gps_coordinates, log_entries
263
  frame_count = 0
 
270
  if video is None:
271
  log_entries.append("Error: No video uploaded")
272
  logging.error("No video uploaded")
273
+ return None, json.dumps({"error": "No video uploaded"}, indent=2), "\n".join(log_entries), [], None, None, None
274
 
275
  start_time = time.time()
276
  cap = cv2.VideoCapture(video)
277
  if not cap.isOpened():
278
  log_entries.append("Error: Could not open video file")
279
  logging.error("Could not open video file")
280
+ return None, json.dumps({"error": "Could not open video file"}, indent=2), "\n".join(log_entries), [], None, None, None
281
 
282
  frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
283
  frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
284
  input_resolution = frame_width * frame_height
285
  fps = cap.get(cv2.CAP_PROP_FPS)
286
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
 
 
 
 
287
 
288
  out_width, out_height = resize_width, resize_height
289
  output_path = os.path.join(OUTPUT_DIR, "processed_output.mp4")
 
292
  log_entries.append("Error: Failed to initialize mp4v codec")
293
  logging.error("Failed to initialize mp4v codec")
294
  cap.release()
295
+ return None, json.dumps({"error": "mp4v codec failed"}, indent=2), "\n".join(log_entries), [], None, None, None
296
 
297
  processed_frames = 0
298
  all_detections = []
 
303
  detection_frame_count = 0
304
  output_frame_count = 0
305
  last_annotated_frame = None
 
 
 
 
 
 
306
 
307
  while True:
308
  ret, frame = cap.read()
 
314
  processed_frames += 1
315
  frame_start = time.time()
316
 
 
 
317
  frame = cv2.resize(frame, (out_width, out_height))
318
+ resize_times.append((time.time() - frame_start) * 1000)
319
 
320
  if not check_image_quality(frame, input_resolution):
 
321
  continue
322
 
 
323
  inference_start = time.time()
324
  results = model(frame, verbose=False, conf=0.5, iou=0.7)
325
  annotated_frame = results[0].plot()
 
344
  "box": box,
345
  "conf": conf,
346
  "gps": gps_coord,
347
+ "timestamp": timestamp_str,
348
+ "frame": frame_count,
349
+ "path": os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count:06d}.jpg")
350
  })
351
+ log_entries.append(f"Frame {frame_count} at {timestamp_str}: Detected {label} with confidence {conf:.2f}")
352
+ logging.info(f"Frame {frame_count} at {timestamp_str}: Detected {label} with confidence {conf:.2f}")
 
353
 
354
  if frame_detections:
355
  detection_frame_count += 1
 
358
  if cv2.imwrite(captured_frame_path, annotated_frame):
359
  if write_geotag(captured_frame_path, gps_coord):
360
  detected_issues.append(captured_frame_path)
 
 
 
 
 
 
361
  if len(detected_issues) > 100:
362
  detected_issues.pop(0)
363
  else:
 
367
  logging.error(f"Failed to save {captured_frame_path}")
368
 
369
  flight_log_path = write_flight_log(frame_count, gps_coord, timestamp_str)
 
 
 
 
 
 
370
  io_times.append((time.time() - io_start) * 1000)
371
 
372
  out.write(annotated_frame)
 
380
  detected_counts.append(len(frame_detections))
381
  all_detections.extend(frame_detections)
382
 
383
+ frame_times.append((time.time() - frame_start) * 1000)
 
 
384
  if len(log_entries) > 50:
385
  log_entries.pop(0)
386
 
 
394
  output_frame_count += 1
395
 
396
  last_metrics = update_metrics(all_detections)
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  cap.release()
399
  out.release()
 
405
  cap.release()
406
 
407
  total_time = time.time() - start_time
 
 
 
 
408
  log_entries.append(f"Output video: {output_frames} frames, {output_fps:.2f} FPS, {output_duration:.2f} seconds")
409
  logging.info(f"Output video: {output_frames} frames, {output_fps:.2f} FPS, {output_duration:.2f} seconds")
 
 
 
 
410
 
411
  chart_path = generate_line_chart()
412
  map_path = generate_map(gps_coordinates[-5:], all_detections)
413
 
414
+ report_path = generate_report(
 
 
 
 
415
  last_metrics,
416
+ detected_issues,
417
+ gps_coordinates,
418
+ all_detections,
419
+ frame_count,
420
+ total_time,
421
+ output_frames,
422
+ output_fps,
423
+ output_duration,
424
+ detection_frame_count,
425
  chart_path,
426
  map_path
427
  )
428
 
429
+ return (
430
+ output_path,
431
+ json.dumps(last_metrics, indent=2),
432
+ "\n".join(log_entries[-10:]),
433
+ detected_issues,
434
+ chart_path,
435
+ map_path,
436
+ report_path
437
+ )
438
+
439
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
440
  gr.Markdown("# NHAI Road Defect Detection Dashboard")
441
  with gr.Row():
442
  with gr.Column(scale=3):
443
+ video_input = gr.Video(label="Upload Video (12MP recommended)")
444
  width_slider = gr.Slider(320, 4000, value=4000, label="Output Width", step=1)
445
  height_slider = gr.Slider(240, 3000, value=3000, label="Output Height", step=1)
446
  skip_slider = gr.Slider(1, 10, value=5, label="Frame Skip", step=1)
 
458
  with gr.Row():
459
  gr.Markdown("## Download Results")
460
  with gr.Row():
461
+ report_download = gr.File(label="Download Analysis Report (Markdown)")
462
 
463
  process_btn.click(
464
  fn=process_video,
465
  inputs=[video_input, width_slider, height_slider, skip_slider],
466
+ outputs=[
467
+ video_output,
468
+ metrics_output,
469
+ logs_output,
470
+ issue_gallery,
471
+ chart_output,
472
+ map_output,
473
+ report_download
474
+ ]
475
  )
476
 
477
  if __name__ == "__main__":
478
+ iface.launch()