Buckets:
| """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.