Update app.py
Browse files
app.py
CHANGED
|
@@ -1155,7 +1155,99 @@ IOA_SUPPRESSION_THRESHOLD = 0.7
|
|
| 1155 |
# --- BOX COMBINATION LOGIC (PURE VERTICAL FIX) ---
|
| 1156 |
# ============================================================================
|
| 1157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1158 |
def calculate_iou(box1, box2):
|
|
|
|
| 1159 |
x1_a, y1_a, x2_a, y2_a = box1
|
| 1160 |
x1_b, y1_b, x2_b, y2_b = box2
|
| 1161 |
x_left = max(x1_a, x1_b)
|
|
@@ -1169,67 +1261,143 @@ def calculate_iou(box1, box2):
|
|
| 1169 |
return intersection_area / union_area if union_area > 0 else 0
|
| 1170 |
|
| 1171 |
|
| 1172 |
-
def
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
| 1176 |
-
|
| 1177 |
-
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
box_a = detections[i]['coords']
|
| 1184 |
-
for j in range(i + 1, len(detections)):
|
| 1185 |
-
if is_suppressed[j]: continue
|
| 1186 |
-
box_b = detections[j]['coords']
|
| 1187 |
-
x_left = max(box_a[0], box_b[0])
|
| 1188 |
-
y_top = max(box_a[1], box_b[1])
|
| 1189 |
-
x_right = min(box_a[2], box_b[2])
|
| 1190 |
-
y_bottom = min(box_a[3], box_b[3])
|
| 1191 |
-
intersection = max(0, x_right - x_left) * max(0, y_bottom - y_top)
|
| 1192 |
-
area_b = detections[j]['area']
|
| 1193 |
-
if area_b > 0 and intersection / area_b > ioa_threshold:
|
| 1194 |
-
is_suppressed[j] = True
|
| 1195 |
-
return [detections[i] for i in keep_indices]
|
| 1196 |
|
| 1197 |
|
| 1198 |
-
# --- UPDATED: page_width argument removed ---
|
| 1199 |
def merge_overlapping_boxes(detections, iou_threshold):
|
| 1200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1201 |
detections.sort(key=lambda d: d['conf'], reverse=True)
|
|
|
|
| 1202 |
merged_detections = []
|
| 1203 |
is_merged = [False] * len(detections)
|
| 1204 |
|
| 1205 |
for i in range(len(detections)):
|
| 1206 |
-
if is_merged[i]:
|
|
|
|
|
|
|
| 1207 |
current_box = detections[i]['coords']
|
| 1208 |
current_class = detections[i]['class']
|
| 1209 |
merged_x1, merged_y1, merged_x2, merged_y2 = current_box
|
|
|
|
|
|
|
| 1210 |
for j in range(i + 1, len(detections)):
|
| 1211 |
-
if is_merged[j] or detections[j]['class'] != current_class:
|
|
|
|
|
|
|
| 1212 |
other_box = detections[j]['coords']
|
| 1213 |
iou = calculate_iou(current_box, other_box)
|
|
|
|
| 1214 |
if iou > iou_threshold:
|
|
|
|
| 1215 |
merged_x1 = min(merged_x1, other_box[0])
|
| 1216 |
merged_y1 = min(merged_y1, other_box[1])
|
| 1217 |
merged_x2 = max(merged_x2, other_box[2])
|
| 1218 |
-
merged_y2 = max(merged_y2, other_box[3])
|
| 1219 |
is_merged[j] = True
|
|
|
|
| 1220 |
merged_detections.append({
|
| 1221 |
'coords': (merged_x1, merged_y1, merged_x2, merged_y2),
|
| 1222 |
-
'y1': merged_y1,
|
| 1223 |
-
'class': current_class,
|
| 1224 |
'conf': detections[i]['conf']
|
| 1225 |
})
|
| 1226 |
|
| 1227 |
-
#
|
| 1228 |
-
# Sort ONLY by the top y-coordinate (coords[1]).
|
| 1229 |
-
# This ignores horizontal position and any complex layout.
|
| 1230 |
merged_detections.sort(key=lambda d: d['coords'][1])
|
| 1231 |
|
| 1232 |
return merged_detections
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1233 |
|
| 1234 |
# ============================================================================
|
| 1235 |
# --- UTILITY FUNCTIONS (Retained) ---
|
|
|
|
| 1155 |
# --- BOX COMBINATION LOGIC (PURE VERTICAL FIX) ---
|
| 1156 |
# ============================================================================
|
| 1157 |
|
| 1158 |
+
# def calculate_iou(box1, box2):
|
| 1159 |
+
# x1_a, y1_a, x2_a, y2_a = box1
|
| 1160 |
+
# x1_b, y1_b, x2_b, y2_b = box2
|
| 1161 |
+
# x_left = max(x1_a, x1_b)
|
| 1162 |
+
# y_top = max(y1_a, y1_b)
|
| 1163 |
+
# x_right = min(x2_a, x2_b)
|
| 1164 |
+
# y_bottom = min(y2_a, y2_b)
|
| 1165 |
+
# intersection_area = max(0, x_right - x_left) * max(0, y_bottom - y_top)
|
| 1166 |
+
# box_a_area = (x2_a - x1_a) * (y2_a - y1_a)
|
| 1167 |
+
# box_b_area = (x2_b - x1_b) * (y2_b - y1_b)
|
| 1168 |
+
# union_area = float(box_a_area + box_b_area - intersection_area)
|
| 1169 |
+
# return intersection_area / union_area if union_area > 0 else 0
|
| 1170 |
+
|
| 1171 |
+
|
| 1172 |
+
# def filter_nested_boxes(detections, ioa_threshold=0.80):
|
| 1173 |
+
# if not detections: return []
|
| 1174 |
+
# for d in detections:
|
| 1175 |
+
# x1, y1, x2, y2 = d['coords']
|
| 1176 |
+
# d['area'] = (x2 - x1) * (y2 - y1)
|
| 1177 |
+
# detections.sort(key=lambda x: x['area'], reverse=True)
|
| 1178 |
+
# keep_indices = []
|
| 1179 |
+
# is_suppressed = [False] * len(detections)
|
| 1180 |
+
# for i in range(len(detections)):
|
| 1181 |
+
# if is_suppressed[i]: continue
|
| 1182 |
+
# keep_indices.append(i)
|
| 1183 |
+
# box_a = detections[i]['coords']
|
| 1184 |
+
# for j in range(i + 1, len(detections)):
|
| 1185 |
+
# if is_suppressed[j]: continue
|
| 1186 |
+
# box_b = detections[j]['coords']
|
| 1187 |
+
# x_left = max(box_a[0], box_b[0])
|
| 1188 |
+
# y_top = max(box_a[1], box_b[1])
|
| 1189 |
+
# x_right = min(box_a[2], box_b[2])
|
| 1190 |
+
# y_bottom = min(box_a[3], box_b[3])
|
| 1191 |
+
# intersection = max(0, x_right - x_left) * max(0, y_bottom - y_top)
|
| 1192 |
+
# area_b = detections[j]['area']
|
| 1193 |
+
# if area_b > 0 and intersection / area_b > ioa_threshold:
|
| 1194 |
+
# is_suppressed[j] = True
|
| 1195 |
+
# return [detections[i] for i in keep_indices]
|
| 1196 |
+
|
| 1197 |
+
|
| 1198 |
+
# # --- UPDATED: page_width argument removed ---
|
| 1199 |
+
# def merge_overlapping_boxes(detections, iou_threshold):
|
| 1200 |
+
# if not detections: return []
|
| 1201 |
+
# detections.sort(key=lambda d: d['conf'], reverse=True)
|
| 1202 |
+
# merged_detections = []
|
| 1203 |
+
# is_merged = [False] * len(detections)
|
| 1204 |
+
|
| 1205 |
+
# for i in range(len(detections)):
|
| 1206 |
+
# if is_merged[i]: continue
|
| 1207 |
+
# current_box = detections[i]['coords']
|
| 1208 |
+
# current_class = detections[i]['class']
|
| 1209 |
+
# merged_x1, merged_y1, merged_x2, merged_y2 = current_box
|
| 1210 |
+
# for j in range(i + 1, len(detections)):
|
| 1211 |
+
# if is_merged[j] or detections[j]['class'] != current_class: continue
|
| 1212 |
+
# other_box = detections[j]['coords']
|
| 1213 |
+
# iou = calculate_iou(current_box, other_box)
|
| 1214 |
+
# if iou > iou_threshold:
|
| 1215 |
+
# merged_x1 = min(merged_x1, other_box[0])
|
| 1216 |
+
# merged_y1 = min(merged_y1, other_box[1])
|
| 1217 |
+
# merged_x2 = max(merged_x2, other_box[2])
|
| 1218 |
+
# merged_y2 = max(merged_y2, other_box[3])
|
| 1219 |
+
# is_merged[j] = True
|
| 1220 |
+
# merged_detections.append({
|
| 1221 |
+
# 'coords': (merged_x1, merged_y1, merged_x2, merged_y2),
|
| 1222 |
+
# 'y1': merged_y1,
|
| 1223 |
+
# 'class': current_class,
|
| 1224 |
+
# 'conf': detections[i]['conf']
|
| 1225 |
+
# })
|
| 1226 |
+
|
| 1227 |
+
# # --- PURE VERTICAL FIX IMPLEMENTATION ---
|
| 1228 |
+
# # Sort ONLY by the top y-coordinate (coords[1]).
|
| 1229 |
+
# # This ignores horizontal position and any complex layout.
|
| 1230 |
+
# merged_detections.sort(key=lambda d: d['coords'][1])
|
| 1231 |
+
|
| 1232 |
+
# return merged_detections
|
| 1233 |
+
|
| 1234 |
+
|
| 1235 |
+
|
| 1236 |
+
|
| 1237 |
+
|
| 1238 |
+
|
| 1239 |
+
|
| 1240 |
+
|
| 1241 |
+
|
| 1242 |
+
|
| 1243 |
+
|
| 1244 |
+
|
| 1245 |
+
|
| 1246 |
+
|
| 1247 |
+
|
| 1248 |
+
|
| 1249 |
def calculate_iou(box1, box2):
|
| 1250 |
+
"""Calculate Intersection over Union between two boxes."""
|
| 1251 |
x1_a, y1_a, x2_a, y2_a = box1
|
| 1252 |
x1_b, y1_b, x2_b, y2_b = box2
|
| 1253 |
x_left = max(x1_a, x1_b)
|
|
|
|
| 1261 |
return intersection_area / union_area if union_area > 0 else 0
|
| 1262 |
|
| 1263 |
|
| 1264 |
+
def calculate_ioa(box1, box2):
|
| 1265 |
+
"""Calculate Intersection over Area of box2."""
|
| 1266 |
+
x1_a, y1_a, x2_a, y2_a = box1
|
| 1267 |
+
x1_b, y1_b, x2_b, y2_b = box2
|
| 1268 |
+
x_left = max(x1_a, x1_b)
|
| 1269 |
+
y_top = max(y1_a, y1_b)
|
| 1270 |
+
x_right = min(x2_a, x2_b)
|
| 1271 |
+
y_bottom = min(y2_a, y2_b)
|
| 1272 |
+
intersection_area = max(0, x_right - x_left) * max(0, y_bottom - y_top)
|
| 1273 |
+
box_a_area = (x2_a - x1_a) * (y2_a - y1_a)
|
| 1274 |
+
return intersection_area / box_a_area if box_a_area > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1275 |
|
| 1276 |
|
|
|
|
| 1277 |
def merge_overlapping_boxes(detections, iou_threshold):
|
| 1278 |
+
"""
|
| 1279 |
+
Merges overlapping boxes of the same class based on IOU threshold.
|
| 1280 |
+
Returns boxes sorted by y-coordinate (top to bottom).
|
| 1281 |
+
"""
|
| 1282 |
+
if not detections:
|
| 1283 |
+
return []
|
| 1284 |
+
|
| 1285 |
+
# Sort by confidence (highest first) for merge priority
|
| 1286 |
detections.sort(key=lambda d: d['conf'], reverse=True)
|
| 1287 |
+
|
| 1288 |
merged_detections = []
|
| 1289 |
is_merged = [False] * len(detections)
|
| 1290 |
|
| 1291 |
for i in range(len(detections)):
|
| 1292 |
+
if is_merged[i]:
|
| 1293 |
+
continue
|
| 1294 |
+
|
| 1295 |
current_box = detections[i]['coords']
|
| 1296 |
current_class = detections[i]['class']
|
| 1297 |
merged_x1, merged_y1, merged_x2, merged_y2 = current_box
|
| 1298 |
+
|
| 1299 |
+
# Try to merge with all subsequent boxes of same class
|
| 1300 |
for j in range(i + 1, len(detections)):
|
| 1301 |
+
if is_merged[j] or detections[j]['class'] != current_class:
|
| 1302 |
+
continue
|
| 1303 |
+
|
| 1304 |
other_box = detections[j]['coords']
|
| 1305 |
iou = calculate_iou(current_box, other_box)
|
| 1306 |
+
|
| 1307 |
if iou > iou_threshold:
|
| 1308 |
+
# Expand merged box to encompass both
|
| 1309 |
merged_x1 = min(merged_x1, other_box[0])
|
| 1310 |
merged_y1 = min(merged_y1, other_box[1])
|
| 1311 |
merged_x2 = max(merged_x2, other_box[2])
|
| 1312 |
+
merged_y2 = max(merged_y2, other_box[3]) # ← FIX THE TYPO HERE
|
| 1313 |
is_merged[j] = True
|
| 1314 |
+
|
| 1315 |
merged_detections.append({
|
| 1316 |
'coords': (merged_x1, merged_y1, merged_x2, merged_y2),
|
| 1317 |
+
'y1': merged_y1,
|
| 1318 |
+
'class': current_class,
|
| 1319 |
'conf': detections[i]['conf']
|
| 1320 |
})
|
| 1321 |
|
| 1322 |
+
# Sort by y-coordinate (top to bottom) for consistent ordering
|
|
|
|
|
|
|
| 1323 |
merged_detections.sort(key=lambda d: d['coords'][1])
|
| 1324 |
|
| 1325 |
return merged_detections
|
| 1326 |
+
|
| 1327 |
+
|
| 1328 |
+
def filter_nested_boxes(detections, ioa_threshold=0.80):
|
| 1329 |
+
"""
|
| 1330 |
+
Removes boxes that are nested inside larger boxes.
|
| 1331 |
+
Keeps the larger (parent) box and suppresses smaller (child) boxes.
|
| 1332 |
+
"""
|
| 1333 |
+
if not detections:
|
| 1334 |
+
return []
|
| 1335 |
+
|
| 1336 |
+
# Calculate area for all detections
|
| 1337 |
+
for d in detections:
|
| 1338 |
+
x1, y1, x2, y2 = d['coords']
|
| 1339 |
+
d['area'] = (x2 - x1) * (y2 - y1)
|
| 1340 |
+
|
| 1341 |
+
# Sort by area (largest first) to prioritize keeping parent boxes
|
| 1342 |
+
detections.sort(key=lambda x: x['area'], reverse=True)
|
| 1343 |
+
|
| 1344 |
+
keep_indices = []
|
| 1345 |
+
is_suppressed = [False] * len(detections)
|
| 1346 |
+
|
| 1347 |
+
for i in range(len(detections)):
|
| 1348 |
+
if is_suppressed[i]:
|
| 1349 |
+
continue
|
| 1350 |
+
|
| 1351 |
+
keep_indices.append(i)
|
| 1352 |
+
box_a = detections[i]['coords']
|
| 1353 |
+
|
| 1354 |
+
# Check all smaller boxes
|
| 1355 |
+
for j in range(i + 1, len(detections)):
|
| 1356 |
+
if is_suppressed[j]:
|
| 1357 |
+
continue
|
| 1358 |
+
|
| 1359 |
+
box_b = detections[j]['coords']
|
| 1360 |
+
|
| 1361 |
+
# Calculate intersection
|
| 1362 |
+
x_left = max(box_a[0], box_b[0])
|
| 1363 |
+
y_top = max(box_a[1], box_b[1])
|
| 1364 |
+
x_right = min(box_a[2], box_b[2])
|
| 1365 |
+
y_bottom = min(box_a[3], box_b[3])
|
| 1366 |
+
|
| 1367 |
+
intersection = max(0, x_right - x_left) * max(0, y_bottom - y_top)
|
| 1368 |
+
area_b = detections[j]['area']
|
| 1369 |
+
|
| 1370 |
+
# If small box is mostly inside large box, suppress it
|
| 1371 |
+
if area_b > 0 and intersection / area_b > ioa_threshold:
|
| 1372 |
+
is_suppressed[j] = True
|
| 1373 |
+
|
| 1374 |
+
# Return kept detections in original y-sorted order
|
| 1375 |
+
kept_detections = [detections[i] for i in keep_indices]
|
| 1376 |
+
kept_detections.sort(key=lambda d: d['coords'][1])
|
| 1377 |
+
|
| 1378 |
+
return kept_detections
|
| 1379 |
+
|
| 1380 |
+
|
| 1381 |
+
|
| 1382 |
+
|
| 1383 |
+
|
| 1384 |
+
|
| 1385 |
+
|
| 1386 |
+
|
| 1387 |
+
|
| 1388 |
+
|
| 1389 |
+
|
| 1390 |
+
|
| 1391 |
+
|
| 1392 |
+
|
| 1393 |
+
|
| 1394 |
+
|
| 1395 |
+
|
| 1396 |
+
|
| 1397 |
+
|
| 1398 |
+
|
| 1399 |
+
|
| 1400 |
+
|
| 1401 |
|
| 1402 |
# ============================================================================
|
| 1403 |
# --- UTILITY FUNCTIONS (Retained) ---
|