hardiksharma6555 commited on
Commit
18d55a0
·
verified ·
1 Parent(s): 4dd531f

Create utils.py

Browse files
Files changed (1) hide show
  1. utils.py +352 -0
utils.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ═══════════════════════════════════════════════════════════════════
2
+ # utils.py - Measurements, HTML Generation, and Utilities
3
+ # ═══════════════════════════════════════════════════════════════════
4
+
5
+ import cv2
6
+ import numpy as np
7
+ from datetime import timedelta
8
+
9
+ # ═══════════════════════════════════════════════════════════════════
10
+ # MEASUREMENT CALCULATOR
11
+ # ═══════════════════════════════════════════════════════════════════
12
+
13
+ class PotholeMeasurementSystem:
14
+ """Calculate physical measurements from segmentation masks"""
15
+
16
+ def __init__(self):
17
+ self.fx = 384.0
18
+ self.fy = 384.0
19
+ self.cx = 320.0
20
+ self.cy = 240.0
21
+
22
+ def calculate_measurements(self, mask, depth_map=None):
23
+ """Calculate all physical measurements from mask"""
24
+ mask_bool = mask > 0
25
+
26
+ if not np.any(mask_bool):
27
+ return None
28
+
29
+ contours, _ = cv2.findContours(
30
+ mask.astype(np.uint8),
31
+ cv2.RETR_EXTERNAL,
32
+ cv2.CHAIN_APPROX_NONE
33
+ )
34
+
35
+ if len(contours) == 0:
36
+ return None
37
+
38
+ contour = max(contours, key=cv2.contourArea)
39
+
40
+ # Get bounding box and centroid
41
+ x, y, w, h = cv2.boundingRect(contour)
42
+ M = cv2.moments(contour)
43
+ if M["m00"] != 0:
44
+ cx = int(M["m10"] / M["m00"])
45
+ cy = int(M["m01"] / M["m00"])
46
+ else:
47
+ cx, cy = x + w//2, y + h//2
48
+
49
+ pixel_area = np.sum(mask_bool)
50
+ perimeter_pixels = cv2.arcLength(contour, True)
51
+ pixel_to_mm = 0.5
52
+
53
+ if depth_map is not None:
54
+ h_c = np.median(depth_map[~mask_bool & (depth_map > 0)])
55
+ pothole_depths = depth_map[mask_bool]
56
+ pothole_depths = pothole_depths[pothole_depths > 0]
57
+
58
+ if len(pothole_depths) > 0:
59
+ actual_depths = pothole_depths - h_c
60
+ max_depth_mm = float(actual_depths.max()) if len(actual_depths) > 0 else 0
61
+ mean_depth_mm = float(actual_depths.mean()) if len(actual_depths) > 0 else 0
62
+
63
+ depth_m = h_c / 1000.0
64
+ s_x = depth_m / self.fx
65
+ s_y = depth_m / self.fy
66
+ pixel_to_mm = (s_x + s_y) / 2 * 1000
67
+ else:
68
+ max_depth_mm = 0
69
+ mean_depth_mm = 0
70
+ else:
71
+ estimated_depth_cm = min(15, (pixel_area / 1000) * 2)
72
+ max_depth_mm = estimated_depth_cm * 10
73
+ mean_depth_mm = max_depth_mm * 0.7
74
+
75
+ perimeter_cm = (perimeter_pixels * pixel_to_mm) / 10
76
+ area_cm2 = (pixel_area * pixel_to_mm * pixel_to_mm) / 100
77
+ area_m2 = area_cm2 / 10000
78
+ volume_liters = (area_m2 * (max_depth_mm / 1000) / 3) * 1000
79
+
80
+ depth_cm = max_depth_mm / 10
81
+ if depth_cm > 10 or area_m2 > 0.5:
82
+ severity = 'CRITICAL'
83
+ severity_color = '🔴'
84
+ elif depth_cm > 5 or area_m2 > 0.2:
85
+ severity = 'HIGH'
86
+ severity_color = '🟠'
87
+ elif depth_cm > 3:
88
+ severity = 'MEDIUM'
89
+ severity_color = '🟡'
90
+ else:
91
+ severity = 'LOW'
92
+ severity_color = '🟢'
93
+
94
+ return {
95
+ 'max_depth_mm': max_depth_mm,
96
+ 'max_depth_cm': max_depth_mm / 10,
97
+ 'mean_depth_mm': mean_depth_mm,
98
+ 'mean_depth_cm': mean_depth_mm / 10,
99
+ 'perimeter_cm': perimeter_cm,
100
+ 'perimeter_m': perimeter_cm / 100,
101
+ 'area_cm2': area_cm2,
102
+ 'area_m2': area_m2,
103
+ 'volume_liters': volume_liters,
104
+ 'volume_m3': volume_liters / 1000,
105
+ 'num_pixels': int(pixel_area),
106
+ 'severity': severity,
107
+ 'severity_color': severity_color,
108
+ 'contour': contour,
109
+ 'bbox': (x, y, w, h),
110
+ 'centroid': (cx, cy)
111
+ }
112
+
113
+ # ═══════════════════════════════════════════════════════════════════
114
+ # HTML GENERATION
115
+ # ═══════════════════════════════════════════════════════════════════
116
+
117
+ def generate_metrics_html(measurements):
118
+ """Generate HTML metrics for images"""
119
+ if not measurements:
120
+ return "<h3 style='color: orange;'>No measurements available</h3>"
121
+
122
+ html = """
123
+ <style>
124
+ .metrics-container {
125
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
126
+ padding: 20px;
127
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
128
+ border-radius: 15px;
129
+ color: white;
130
+ }
131
+ .metrics-header {
132
+ text-align: center;
133
+ margin-bottom: 20px;
134
+ font-size: 24px;
135
+ font-weight: bold;
136
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
137
+ }
138
+ .pothole-card {
139
+ background: rgba(255, 255, 255, 0.95);
140
+ border-radius: 10px;
141
+ padding: 15px;
142
+ margin: 10px 0;
143
+ color: #333;
144
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
145
+ }
146
+ .pothole-header {
147
+ font-size: 18px;
148
+ font-weight: bold;
149
+ margin-bottom: 10px;
150
+ display: flex;
151
+ justify-content: space-between;
152
+ align-items: center;
153
+ }
154
+ .severity-badge {
155
+ padding: 5px 15px;
156
+ border-radius: 20px;
157
+ font-weight: bold;
158
+ font-size: 14px;
159
+ }
160
+ .severity-CRITICAL { background: #ff4444; color: white; }
161
+ .severity-HIGH { background: #ff9800; color: white; }
162
+ .severity-MEDIUM { background: #ffeb3b; color: #333; }
163
+ .severity-LOW { background: #4caf50; color: white; }
164
+ .metrics-grid {
165
+ display: grid;
166
+ grid-template-columns: repeat(2, 1fr);
167
+ gap: 10px;
168
+ margin-top: 10px;
169
+ }
170
+ .metric-item {
171
+ background: #f5f5f5;
172
+ padding: 10px;
173
+ border-radius: 5px;
174
+ border-left: 4px solid #667eea;
175
+ }
176
+ .metric-label {
177
+ font-size: 12px;
178
+ color: #666;
179
+ margin-bottom: 3px;
180
+ }
181
+ .metric-value {
182
+ font-size: 18px;
183
+ font-weight: bold;
184
+ color: #333;
185
+ }
186
+ .summary-section {
187
+ margin-top: 20px;
188
+ padding: 15px;
189
+ background: rgba(255, 255, 255, 0.95);
190
+ border-radius: 10px;
191
+ color: #333;
192
+ }
193
+ </style>
194
+
195
+ <div class="metrics-container">
196
+ <div class="metrics-header">🕳️ Pothole Detection Results</div>
197
+ """
198
+
199
+ for m in measurements:
200
+ severity_class = f"severity-{m['severity']}"
201
+ html += f"""
202
+ <div class="pothole-card">
203
+ <div class="pothole-header">
204
+ <span>{m['severity_color']} Pothole #{m['pothole_id']}</span>
205
+ <span class="severity-badge {severity_class}">{m['severity']}</span>
206
+ </div>
207
+ <div style="margin-bottom: 10px;"><strong>Confidence:</strong> {m['confidence']*100:.1f}%</div>
208
+ <div class="metrics-grid">
209
+ <div class="metric-item">
210
+ <div class="metric-label">📏 Max Depth</div>
211
+ <div class="metric-value">{m['max_depth_cm']:.2f} cm</div>
212
+ </div>
213
+ <div class="metric-item">
214
+ <div class="metric-label">📦 Area</div>
215
+ <div class="metric-value">{m['area_m2']:.4f} m²</div>
216
+ </div>
217
+ <div class="metric-item">
218
+ <div class="metric-label">💧 Volume</div>
219
+ <div class="metric-value">{m['volume_liters']:.2f} L</div>
220
+ </div>
221
+ <div class="metric-item">
222
+ <div class="metric-label">📍 Centroid</div>
223
+ <div class="metric-value">({m['centroid'][0]}, {m['centroid'][1]})</div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ """
228
+
229
+ total_area = sum(m['area_m2'] for m in measurements)
230
+ total_volume = sum(m['volume_liters'] for m in measurements)
231
+ avg_depth = np.mean([m['max_depth_cm'] for m in measurements])
232
+
233
+ html += f"""
234
+ <div class="summary-section">
235
+ <h3>📊 Overall Summary</h3>
236
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
237
+ <div><strong>Total Potholes:</strong> {len(measurements)}</div>
238
+ <div><strong>Average Depth:</strong> {avg_depth:.2f} cm</div>
239
+ <div><strong>Total Area:</strong> {total_area:.4f} m²</div>
240
+ <div><strong>Total Volume:</strong> {total_volume:.2f} L</div>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ """
245
+
246
+ return html
247
+
248
+ def generate_video_metrics_html(stats, total_frames, fps):
249
+ """Generate HTML metrics for video"""
250
+ if stats['total_potholes'] == 0:
251
+ return "<h3 style='color: orange;'>⚠️ No potholes detected</h3>"
252
+
253
+ duration_str = str(timedelta(seconds=int(total_frames / fps)))
254
+
255
+ html = f"""
256
+ <style>
257
+ .video-metrics {{
258
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
259
+ padding: 20px;
260
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
261
+ border-radius: 15px;
262
+ color: white;
263
+ }}
264
+ .video-summary {{
265
+ background: rgba(255, 255, 255, 0.95);
266
+ border-radius: 10px;
267
+ padding: 15px;
268
+ margin: 10px 0;
269
+ color: #333;
270
+ }}
271
+ .pothole-track {{
272
+ background: rgba(255, 255, 255, 0.95);
273
+ border-radius: 10px;
274
+ padding: 15px;
275
+ margin: 10px 0;
276
+ color: #333;
277
+ }}
278
+ .severity-badge {{
279
+ padding: 5px 15px;
280
+ border-radius: 20px;
281
+ font-weight: bold;
282
+ }}
283
+ .severity-CRITICAL {{ background: #ff4444; color: white; }}
284
+ .severity-HIGH {{ background: #ff9800; color: white; }}
285
+ .severity-MEDIUM {{ background: #ffeb3b; color: #333; }}
286
+ .severity-LOW {{ background: #4caf50; color: white; }}
287
+ </style>
288
+
289
+ <div class="video-metrics">
290
+ <div style="text-align: center; margin-bottom: 20px; font-size: 24px; font-weight: bold;">
291
+ 🎥 Video Report
292
+ </div>
293
+ <div class="video-summary">
294
+ <h3>📊 Summary</h3>
295
+ <p><strong>Duration:</strong> {duration_str} | <strong>Potholes:</strong> {stats['total_potholes']}</p>
296
+ </div>
297
+ """
298
+
299
+ for pothole in sorted(stats['potholes'], key=lambda x: x['track_id']):
300
+ severity_class = f"severity-{pothole['severity']}"
301
+ html += f"""
302
+ <div class="pothole-track">
303
+ <div style="display: flex; justify-content: space-between;">
304
+ <span>🕳️ ID: {pothole['track_id']}</span>
305
+ <span class="severity-badge {severity_class}">{pothole['severity']}</span>
306
+ </div>
307
+ <p><strong>Frames:</strong> {pothole['frames_detected']} |
308
+ <strong>Max Depth:</strong> {pothole['max_depth_cm']:.2f} cm |
309
+ <strong>Max Volume:</strong> {pothole['max_volume_liters']:.2f} L</p>
310
+ </div>
311
+ """
312
+
313
+ html += "</div>"
314
+ return html
315
+
316
+ def generate_summary_text(measurements):
317
+ """Generate text summary for images"""
318
+ if not measurements:
319
+ return "No potholes detected."
320
+
321
+ summary = f"🔍 DETECTION SUMMARY\n{'='*50}\n\n"
322
+ summary += f"Total Potholes: {len(measurements)}\n\n"
323
+
324
+ for m in measurements:
325
+ summary += f"{m['severity_color']} Pothole #{m['pothole_id']} - {m['severity']}\n"
326
+ summary += f" Confidence: {m['confidence']*100:.1f}%\n"
327
+ summary += f" Depth: {m['max_depth_cm']:.2f} cm\n"
328
+ summary += f" Area: {m['area_m2']:.4f} m²\n"
329
+ summary += f" Volume: {m['volume_liters']:.2f} L\n"
330
+ summary += f" Centroid: ({m['centroid'][0]}, {m['centroid'][1]})\n\n"
331
+
332
+ return summary
333
+
334
+ def generate_video_summary_text(stats, total_frames, fps):
335
+ """Generate text summary for video"""
336
+ if stats['total_potholes'] == 0:
337
+ return "No potholes detected in video."
338
+
339
+ duration_str = str(timedelta(seconds=int(total_frames / fps)))
340
+
341
+ summary = f"🎥 VIDEO REPORT\n{'='*70}\n\n"
342
+ summary += f"Duration: {duration_str} | Frames: {total_frames:,} | FPS: {fps}\n"
343
+ summary += f"Unique Potholes: {stats['total_potholes']}\n\n"
344
+
345
+ for pothole in sorted(stats['potholes'], key=lambda x: x['track_id']):
346
+ summary += f"🕳️ ID {pothole['track_id']} - {pothole['severity']}\n"
347
+ summary += f" Frames: {pothole['frames_detected']}\n"
348
+ summary += f" First: Frame {pothole['first_frame']} ({pothole['first_timestamp']})\n"
349
+ summary += f" Max Depth: {pothole['max_depth_cm']:.2f} cm\n"
350
+ summary += f" Max Volume: {pothole['max_volume_liters']:.2f} L\n\n"
351
+
352
+ return summary