nagasurendra commited on
Commit
bf5ec9c
·
verified ·
1 Parent(s): b4186f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -81
app.py CHANGED
@@ -1,5 +1,3 @@
1
- import asyncio
2
- import platform
3
  import cv2
4
  import torch
5
  import gradio as gr
@@ -14,15 +12,14 @@ from typing import List, Dict, Any, Optional
14
  from ultralytics import YOLO
15
  import ultralytics
16
  import time
17
- import exiftool
18
- import csv
19
 
20
  # Set YOLO config directory
21
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
22
 
23
  # Set up logging
24
  logging.basicConfig(
25
- filename="drone_app.log",
26
  level=logging.INFO,
27
  format="%(asctime)s - %(levelname)s - %(message)s"
28
  )
@@ -47,23 +44,8 @@ last_metrics: Dict[str, Any] = {}
47
  frame_count: int = 0
48
  SAVE_IMAGE_INTERVAL = 1 # Save every frame with detections
49
 
50
- # SOP Parameters from Annexure-I
51
- DRONE_SPEED_MS = 5 # 5 m/s (18 km/hr)
52
- MIN_SATELLITES = 12
53
- IMAGE_OVERLAP = 0.85 # 85% front and side overlap
54
- MIN_RESOLUTION_MP = 12 # Minimum 12 MP
55
- RECORDING_ANGLE = 90 # Nadir (90 degrees)
56
- IMAGE_FORMAT = "JPEG"
57
-
58
- # Annexure-III Operations and Maintenance parameters
59
- DETECTION_CLASSES = [
60
- "Potholes", "Edge Drops", "Crack", "Raveling", "Rain Cut Embankments",
61
- "Authorized Median Opening", "Unauthorized Median Opening",
62
- "Intersection/Crossroads", "Temporary Encroachments", "Permanent Encroachments",
63
- "Missing Lane Markings", "Missing Boundary Wall", "Damaged Boundary Wall",
64
- "Open Drain", "Covered Drain", "Blocked Drain", "Unclean Drain",
65
- "Missing Dissipation Basin"
66
- ]
67
 
68
  # Debug: Check environment
69
  print(f"Torch version: {torch.__version__}")
@@ -74,7 +56,7 @@ print(f"CUDA available: {torch.cuda.is_available()}")
74
  # Load custom YOLO model
75
  device = "cuda" if torch.cuda.is_available() else "cpu"
76
  print(f"Using device: {device}")
77
- model = YOLO('./data/best.pt').to(device) # Assumes model is trained for all DETECTION_CLASSES
78
  if device == "cuda":
79
  model.half() # Use half-precision (FP16)
80
  print(f"Model classes: {model.names}")
@@ -93,54 +75,51 @@ def generate_map(gps_coords: List[List[float]], items: List[Dict[str, Any]]) ->
93
 
94
  def write_geotag(image_path: str, gps_coord: List[float]) -> bool:
95
  try:
96
- with exiftool.ExifToolHelper() as et:
97
- et.set_tags(
98
- [image_path],
99
- {
100
- "EXIF:GPSLatitude": gps_coord[0],
101
- "EXIF:GPSLongitude": gps_coord[1],
102
- "EXIF:GPSLatitudeRef": "N" if gps_coord[0] >= 0 else "S",
103
- "EXIF:GPSLongitudeRef": "E" if gps_coord[1] >= 0 else "W"
104
- }
105
- )
 
 
106
  return True
107
  except Exception as e:
108
  logging.error(f"Failed to geotag {image_path}: {str(e)}")
 
109
  return False
110
 
111
  def write_flight_log(frame_count: int, gps_coord: List[float], timestamp: str) -> str:
112
- log_path = os.path.join(FLIGHT_LOG_DIR, f"flight_log_{frame_count}.csv")
113
- with open(log_path, 'w', newline='') as csvfile:
114
- writer = csv.writer(csvfile)
115
- writer.writerow(["Frame", "Timestamp", "Latitude", "Longitude", "Speed_ms", "Satellites", "Altitude_m"])
116
- writer.writerow([frame_count, timestamp, gps_coord[0], gps_coord[1], DRONE_SPEED_MS, MIN_SATELLITES, 60]) # Example altitude
117
- return log_path
118
-
119
- def check_sop_compliance(frame: np.ndarray, gps_coord: List[float], frame_count: int) -> bool:
 
 
 
 
 
120
  height, width, _ = frame.shape
121
- if width * height < MIN_RESOLUTION_MP * 1e6: # Check resolution (12MP)
122
- log_entries.append(f"Frame {frame_count}: Resolution below {MIN_RESOLUTION_MP}MP")
123
- return False
124
- if len(gps_coord) != 2 or not all(isinstance(x, float) for x in gps_coord):
125
- log_entries.append(f"Frame {frame_count}: Invalid GPS coordinates")
126
  return False
127
  return True
128
 
129
  def update_metrics(detections: List[Dict[str, Any]]) -> Dict[str, Any]:
130
  counts = Counter([det["label"] for det in detections])
131
- metrics = {
132
  "items": [{"type": k, "count": v} for k, v in counts.items()],
133
  "total_detections": len(detections),
134
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
135
- "sop_compliance": {
136
- "drone_speed_ms": DRONE_SPEED_MS,
137
- "image_overlap": IMAGE_OVERLAP,
138
- "min_resolution_mp": MIN_RESOLUTION_MP,
139
- "recording_angle_degrees": RECORDING_ANGLE,
140
- "image_format": IMAGE_FORMAT
141
- }
142
  }
143
- return metrics
144
 
145
  def generate_line_chart() -> Optional[str]:
146
  if not detected_counts:
@@ -157,7 +136,7 @@ def generate_line_chart() -> Optional[str]:
157
  plt.close()
158
  return chart_path
159
 
160
- async def process_video(video, resize_width=320, resize_height=240, frame_skip=5):
161
  global frame_count, last_metrics, detected_counts, detected_issues, gps_coordinates, log_entries
162
  frame_count = 0
163
  detected_counts.clear()
@@ -219,7 +198,8 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
219
  data_lake_submission = {
220
  "images": [],
221
  "flight_logs": [],
222
- "analytics": []
 
223
  }
224
 
225
  while True:
@@ -233,6 +213,10 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
233
  frame_start = time.time()
234
 
235
  frame = cv2.resize(frame, (out_width, out_height))
 
 
 
 
236
  results = model(frame, verbose=False, conf=0.5, iou=0.7)
237
  annotated_frame = results[0].plot()
238
 
@@ -240,9 +224,7 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
240
  timestamp_str = f"{int(frame_timestamp // 60)}:{int(frame_timestamp % 60):02d}"
241
 
242
  gps_coord = [17.385044 + (frame_count * 0.0001), 78.486671 + (frame_count * 0.0001)]
243
- if not check_sop_compliance(frame, gps_coord, frame_count):
244
- log_entries.append(f"Frame {frame_count}: SOP compliance check failed")
245
- continue
246
 
247
  frame_detections = []
248
  for detection in results[0].boxes:
@@ -250,7 +232,7 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
250
  conf = float(detection.conf)
251
  box = detection.xyxy[0].cpu().numpy().astype(int).tolist()
252
  label = model.names[cls]
253
- if label in DETECTION_CLASSES:
254
  frame_detections.append({
255
  "label": label,
256
  "box": box,
@@ -266,10 +248,7 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
266
  detection_frame_count += 1
267
  if detection_frame_count % SAVE_IMAGE_INTERVAL == 0:
268
  captured_frame_path = os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count:06d}.jpg")
269
- if not cv2.imwrite(captured_frame_path, annotated_frame):
270
- log_entries.append(f"Error: Failed to save {captured_frame_path}")
271
- logging.error(f"Failed to save {captured_frame_path}")
272
- else:
273
  if write_geotag(captured_frame_path, gps_coord):
274
  detected_issues.append(captured_frame_path)
275
  data_lake_submission["images"].append({
@@ -281,13 +260,17 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
281
  if len(detected_issues) > 100:
282
  detected_issues.pop(0)
283
  else:
284
- log_entries.append(f"Error: Failed to geotag {captured_frame_path}")
 
 
 
285
 
286
  flight_log_path = write_flight_log(frame_count, gps_coord, timestamp_str)
287
- data_lake_submission["flight_logs"].append({
288
- "path": flight_log_path,
289
- "frame": frame_count
290
- })
 
291
 
292
  out.write(annotated_frame)
293
  output_frame_count += 1
@@ -298,7 +281,6 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
298
  output_frame_count += 1
299
 
300
  detected_counts.append(len(frame_detections))
301
- gps_coordinates.append(gps_coord)
302
  all_detections.extend(frame_detections)
303
 
304
  detection_summary = {
@@ -324,8 +306,14 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
324
  data_lake_submission["gps_coordinates"] = gps_coordinates[-1] if gps_coordinates else [0, 0]
325
 
326
  submission_json_path = os.path.join(OUTPUT_DIR, "data_lake_submission.json")
327
- with open(submission_json_path, 'w') as f:
328
- json.dump(data_lake_submission, f, indent=2)
 
 
 
 
 
 
329
 
330
  cap.release()
331
  out.release()
@@ -359,10 +347,10 @@ async def process_video(video, resize_width=320, resize_height=240, frame_skip=5
359
 
360
  # Gradio interface
361
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
362
- gr.Markdown("# NHAI Drone Analytics Dashboard")
363
  with gr.Row():
364
  with gr.Column(scale=3):
365
- video_input = gr.Video(label="Upload Drone Video")
366
  width_slider = gr.Slider(320, 640, value=320, label="Output Width", step=1)
367
  height_slider = gr.Slider(240, 480, value=240, label="Output Height", step=1)
368
  skip_slider = gr.Slider(1, 10, value=5, label="Frame Skip", step=1)
@@ -384,8 +372,5 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
384
  outputs=[video_output, metrics_output, logs_output, issue_gallery, chart_output, map_output]
385
  )
386
 
387
- if platform.system() == "Emscripten":
388
- asyncio.ensure_future(process_video())
389
- else:
390
- if __name__ == "__main__":
391
- iface.launch()
 
 
 
1
  import cv2
2
  import torch
3
  import gradio as gr
 
12
  from ultralytics import YOLO
13
  import ultralytics
14
  import time
15
+ import piexif
 
16
 
17
  # Set YOLO config directory
18
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
19
 
20
  # Set up logging
21
  logging.basicConfig(
22
+ filename="app.log",
23
  level=logging.INFO,
24
  format="%(asctime)s - %(levelname)s - %(message)s"
25
  )
 
44
  frame_count: int = 0
45
  SAVE_IMAGE_INTERVAL = 1 # Save every frame with detections
46
 
47
+ # Detection classes (as per original code)
48
+ DETECTION_CLASSES = ["Longitudinal", "Pothole", "Transverse", "Crack"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  # Debug: Check environment
51
  print(f"Torch version: {torch.__version__}")
 
56
  # Load custom YOLO model
57
  device = "cuda" if torch.cuda.is_available() else "cpu"
58
  print(f"Using device: {device}")
59
+ model = YOLO('./data/best.pt').to(device)
60
  if device == "cuda":
61
  model.half() # Use half-precision (FP16)
62
  print(f"Model classes: {model.names}")
 
75
 
76
  def write_geotag(image_path: str, gps_coord: List[float]) -> bool:
77
  try:
78
+ lat = abs(gps_coord[0])
79
+ lon = abs(gps_coord[1])
80
+ lat_ref = "N" if gps_coord[0] >= 0 else "S"
81
+ lon_ref = "E" if gps_coord[1] >= 0 else "W"
82
+ exif_dict = piexif.load(image_path) if os.path.exists(image_path) else {"GPS": {}}
83
+ exif_dict["GPS"] = {
84
+ piexif.GPSIFD.GPSLatitudeRef: lat_ref,
85
+ piexif.GPSIFD.GPSLatitude: ((int(lat), 1), (0, 1), (0, 1)),
86
+ piexif.GPSIFD.GPSLongitudeRef: lon_ref,
87
+ piexif.GPSIFD.GPSLongitude: ((int(lon), 1), (0, 1), (0, 1))
88
+ }
89
+ piexif.insert(piexif.dump(exif_dict), image_path)
90
  return True
91
  except Exception as e:
92
  logging.error(f"Failed to geotag {image_path}: {str(e)}")
93
+ log_entries.append(f"Error: Failed to geotag {image_path}: {str(e)}")
94
  return False
95
 
96
  def write_flight_log(frame_count: int, gps_coord: List[float], timestamp: str) -> str:
97
+ log_path = os.path.join(FLIGHT_LOG_DIR, f"flight_log_{frame_count:06d}.csv")
98
+ try:
99
+ with open(log_path, 'w', newline='') as csvfile:
100
+ writer = csv.writer(csvfile)
101
+ writer.writerow(["Frame", "Timestamp", "Latitude", "Longitude", "Speed_ms", "Satellites", "Altitude_m"])
102
+ writer.writerow([frame_count, timestamp, gps_coord[0], gps_coord[1], 5.0, 12, 60])
103
+ return log_path
104
+ except Exception as e:
105
+ logging.error(f"Failed to write flight log {log_path}: {str(e)}")
106
+ log_entries.append(f"Error: Failed to write flight log {log_path}: {str(e)}")
107
+ return ""
108
+
109
+ def check_image_quality(frame: np.ndarray) -> bool:
110
  height, width, _ = frame.shape
111
+ if width * height < 12_000_000: # 12 MP requirement
112
+ log_entries.append(f"Frame {frame_count}: Resolution below 12MP")
 
 
 
113
  return False
114
  return True
115
 
116
  def update_metrics(detections: List[Dict[str, Any]]) -> Dict[str, Any]:
117
  counts = Counter([det["label"] for det in detections])
118
+ return {
119
  "items": [{"type": k, "count": v} for k, v in counts.items()],
120
  "total_detections": len(detections),
121
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
 
 
 
 
 
 
122
  }
 
123
 
124
  def generate_line_chart() -> Optional[str]:
125
  if not detected_counts:
 
136
  plt.close()
137
  return chart_path
138
 
139
+ def process_video(video, resize_width=320, resize_height=240, frame_skip=5):
140
  global frame_count, last_metrics, detected_counts, detected_issues, gps_coordinates, log_entries
141
  frame_count = 0
142
  detected_counts.clear()
 
198
  data_lake_submission = {
199
  "images": [],
200
  "flight_logs": [],
201
+ "analytics": [],
202
+ "metrics": {}
203
  }
204
 
205
  while True:
 
213
  frame_start = time.time()
214
 
215
  frame = cv2.resize(frame, (out_width, out_height))
216
+ if not check_image_quality(frame):
217
+ log_entries.append(f"Frame {frame_count}: Skipped due to low resolution")
218
+ continue
219
+
220
  results = model(frame, verbose=False, conf=0.5, iou=0.7)
221
  annotated_frame = results[0].plot()
222
 
 
224
  timestamp_str = f"{int(frame_timestamp // 60)}:{int(frame_timestamp % 60):02d}"
225
 
226
  gps_coord = [17.385044 + (frame_count * 0.0001), 78.486671 + (frame_count * 0.0001)]
227
+ gps_coordinates.append(gps_coord)
 
 
228
 
229
  frame_detections = []
230
  for detection in results[0].boxes:
 
232
  conf = float(detection.conf)
233
  box = detection.xyxy[0].cpu().numpy().astype(int).tolist()
234
  label = model.names[cls]
235
+ if label in DETECTION_CLASSES: # Only process relevant classes
236
  frame_detections.append({
237
  "label": label,
238
  "box": box,
 
248
  detection_frame_count += 1
249
  if detection_frame_count % SAVE_IMAGE_INTERVAL == 0:
250
  captured_frame_path = os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count:06d}.jpg")
251
+ if cv2.imwrite(captured_frame_path, annotated_frame):
 
 
 
252
  if write_geotag(captured_frame_path, gps_coord):
253
  detected_issues.append(captured_frame_path)
254
  data_lake_submission["images"].append({
 
260
  if len(detected_issues) > 100:
261
  detected_issues.pop(0)
262
  else:
263
+ log_entries.append(f"Frame {frame_count}: Geotagging failed")
264
+ else:
265
+ log_entries.append(f"Error: Failed to save {captured_frame_path}")
266
+ logging.error(f"Failed to save {captured_frame_path}")
267
 
268
  flight_log_path = write_flight_log(frame_count, gps_coord, timestamp_str)
269
+ if flight_log_path:
270
+ data_lake_submission["flight_logs"].append({
271
+ "path": flight_log_path,
272
+ "frame": frame_count
273
+ })
274
 
275
  out.write(annotated_frame)
276
  output_frame_count += 1
 
281
  output_frame_count += 1
282
 
283
  detected_counts.append(len(frame_detections))
 
284
  all_detections.extend(frame_detections)
285
 
286
  detection_summary = {
 
306
  data_lake_submission["gps_coordinates"] = gps_coordinates[-1] if gps_coordinates else [0, 0]
307
 
308
  submission_json_path = os.path.join(OUTPUT_DIR, "data_lake_submission.json")
309
+ try:
310
+ with open(submission_json_path, 'w') as f:
311
+ json.dump(data_lake_submission, f, indent=2)
312
+ log_entries.append(f"Submission JSON saved: {submission_json_path}")
313
+ logging.info(f"Submission JSON saved: {submission_json_path}")
314
+ except Exception as e:
315
+ log_entries.append(f"Error: Failed to save submission JSON: {str(e)}")
316
+ logging.error(f"Failed to save submission JSON: {str(e)}")
317
 
318
  cap.release()
319
  out.release()
 
347
 
348
  # Gradio interface
349
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange")) as iface:
350
+ gr.Markdown("# NHAI Road Defect Detection Dashboard")
351
  with gr.Row():
352
  with gr.Column(scale=3):
353
+ video_input = gr.Video(label="Upload Video")
354
  width_slider = gr.Slider(320, 640, value=320, label="Output Width", step=1)
355
  height_slider = gr.Slider(240, 480, value=240, label="Output Height", step=1)
356
  skip_slider = gr.Slider(1, 10, value=5, label="Frame Skip", step=1)
 
372
  outputs=[video_output, metrics_output, logs_output, issue_gallery, chart_output, map_output]
373
  )
374
 
375
+ if __name__ == "__main__":
376
+ iface.launch()