Hsyshasuzh/longmemory-geo / project /evaluation /locate_detection_errors.py
Hsyshasuzh's picture
download
raw
4.67 kB
"""Locate representative tile-level false positives and missed targets."""
import argparse
from collections import defaultdict
import json
from pathlib import Path
from shapely.geometry import Polygon
NAMES = {3: "bridge", 4: "vehicle", 5: "large_vehicle", 6: "aircraft", 7: "vessel", 8: "storage_tank"}
def polygon(points):
coordinates = list(zip(points[::2], points[1::2]))
value = Polygon(coordinates)
return value if value.is_valid else value.buffer(0)
def locate(records: dict) -> dict:
targets = defaultdict(lambda: defaultdict(list))
predictions = defaultdict(lambda: defaultdict(list))
ignored = defaultdict(list)
for row in records["targets"]:
sample_id = row["sample_id"]
ignored[sample_id].extend(row.get("ignored_annotations", []))
for item in row.get("annotations", []):
targets[int(item["category_id"])][sample_id].append(item)
for row in records["predictions"]:
for item in row.get("annotations", []):
predictions[int(item["category_id"])][row["sample_id"]].append(item)
result = {}
for class_id in sorted(set(targets) | set(predictions)):
matched = defaultdict(set)
false_positives = []
for sample_id, items in predictions[class_id].items():
ordered = sorted(items, key=lambda item: float(item["confidence"]), reverse=True)
truth = targets[class_id].get(sample_id, [])
truth_polygons = [polygon(item["obb_polygon"]) for item in truth]
for item in ordered:
prediction_polygon = polygon(item["obb_polygon"])
best_index, best_iou = -1, 0.0
for index, truth_polygon in enumerate(truth_polygons):
if index in matched[sample_id]:
continue
union = prediction_polygon.union(truth_polygon).area
iou = prediction_polygon.intersection(truth_polygon).area / union if union else 0.0
if iou > best_iou:
best_index, best_iou = index, iou
if best_index >= 0 and best_iou >= .5:
matched[sample_id].add(best_index)
continue
ignored_hit = False
for item_ignored in ignored[sample_id]:
ignored_polygon = polygon(item_ignored.get("clipped_polygon", item_ignored.get("obb_polygon", [])))
if ignored_polygon.area and prediction_polygon.intersection(ignored_polygon).area >= prediction_polygon.area * .5:
ignored_hit = True
break
if not ignored_hit:
false_positives.append({"sample_id": sample_id, "confidence": float(item["confidence"]),
"obb_polygon": item["obb_polygon"], "best_iou": best_iou})
false_positives.sort(key=lambda item: item["confidence"], reverse=True)
missed = []
for sample_id, items in targets[class_id].items():
for index, item in enumerate(items):
if index not in matched[sample_id]:
missed.append({"sample_id": sample_id, "obb_polygon": item["obb_polygon"]})
result[str(class_id)] = {"class": NAMES.get(class_id, f"canonical_{class_id}"),
"false_positives": false_positives[:20],
"missed_targets": missed[:20]}
return result
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("soda_records", type=Path)
parser.add_argument("dota_records", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
if args.output.exists():
raise FileExistsError("output already exists")
soda = json.loads(args.soda_records.read_text())
dota = json.loads(args.dota_records.read_text())
report = {"status": "DEVELOPMENT_ERROR_LOCATIONS", "iou_threshold": .5,
"ignore_prediction_fraction": .5,
"soda_a_validation": locate(soda),
"dota_selection": locate(dota["candidate"]),
"limitations": ["Tile-level development records only.",
"No label changes or test data were used.",
"Representative coordinates require visual inspection before interpretation."]}
args.output.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps({"soda_classes": list(report["soda_a_validation"]),
"dota_classes": list(report["dota_selection"])}, indent=2))
if __name__ == "__main__":
main()

Xet Storage Details

Size:
4.67 kB
·
Xet hash:
17c581d40cc1c1b65118600e9128b68a58cd9a54784a6bc0ea8995f2730a402a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.