Spaces:
Running on Zero
Running on Zero
Download src/accident/reporting.py from vish07anth/ATA: direct link, hf CLI and curl.
- Browser
- Download file 8.92 kB
-
https://huggingface.co/spaces/vish07anth/ATA/resolve/main/src/accident/reporting.py
- Command line
-
hf download hf://spaces/vish07anth/ATA/src/accident/reporting.py
-
curl -L -o reporting.py https://huggingface.co/spaces/vish07anth/ATA/resolve/main/src/accident/reporting.py
8.92 kB
| """Markdown/JSON/CSV report writers for Phase 5.""" | |
| from __future__ import annotations | |
| import csv | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| from . import CLASS_ID_ACCIDENT, CLASS_ID_NON_ACCIDENT | |
| def _md_table(headers: list[str], rows: list[list[Any]]) -> str: | |
| """Convert headers and rows to a Markdown table.""" | |
| if not rows: | |
| return "" | |
| lines = ["| " + " | ".join(str(h) for h in headers) + " |"] | |
| lines.append("| " + " | ".join("---" for _ in headers) + " |") | |
| for row in rows: | |
| lines.append("| " + " | ".join(str(v) for v in row) + " |") | |
| return "\n".join(lines) + "\n" | |
| def write_dataset_summary( | |
| stats: dict[str, Any], | |
| duplicate_info: dict[str, Any], | |
| save_path: Path, | |
| ) -> None: | |
| """Write the dataset_training_summary.md report.""" | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| lines = [ | |
| "# Accident Dataset Summary\n", | |
| "This report summarizes the object-level accident dataset used for Phase 5 training.\n", | |
| "## Image-level and Object-level Distributions\n", | |
| ] | |
| headers = [ | |
| "Split", "Images", "Objects", "Accident", "Non-accident", | |
| "Accident %", "Mean objs/img", "Median", "Min", "Max", | |
| ] | |
| rows = [] | |
| for split, s in stats.items(): | |
| rows.append( | |
| [ | |
| split, s["images"], s["objects"], s["accident_objects"], | |
| s["non_accident_objects"], f"{s['accident_pct']}%", | |
| s["mean_objects_per_image"], s["median_objects_per_image"], | |
| s["min_objects_per_image"], s["max_objects_per_image"], | |
| ] | |
| ) | |
| lines.append(_md_table(headers, rows)) | |
| lines.append("\n## Key Observations\n") | |
| lines.append( | |
| "- Object-level class distribution is far more balanced than image-level prevalence.\n" | |
| ) | |
| lines.append( | |
| "- A naive image-level classifier predicting 'accident' for every image would be misleading.\n" | |
| ) | |
| lines.append( | |
| "- The meaningful task is **object-level accident localization**.\n" | |
| ) | |
| lines.append("\n## Duplicate Handling\n") | |
| lines.append( | |
| f"- Training duplicates preserved for the baseline: {duplicate_info.get('train_duplicate_rows', 26)} duplicate rows " | |
| f"({duplicate_info.get('train_unique_duplicate_hashes', 13)} unique hashes).\n" | |
| ) | |
| lines.append( | |
| f"- Cross-split leakage: {duplicate_info.get('cross_split_duplicates', 0)}.\n" | |
| ) | |
| lines.append( | |
| "- Original Parquet files were not modified.\n" | |
| ) | |
| save_path.write_text("".join(lines), encoding="utf-8") | |
| def write_validation_report(result: dict[str, Any], save_path: Path) -> None: | |
| """Write validation_results.md.""" | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| metrics = result["metrics"] | |
| overall = metrics["overall"] | |
| per_class = metrics["per_class"] | |
| lines = [ | |
| "# Validation Results\n", | |
| "Model selected using validation metrics only.\n\n", | |
| "## Overall Metrics\n", | |
| ] | |
| lines.append(_md_table( | |
| ["Metric", "Value"], | |
| [ | |
| ["mAP@50", f"{overall['mAP_50']:.4f}"], | |
| ["mAP@50:95", f"{overall['mAP_50_95']:.4f}"], | |
| ["mAP@75", f"{overall['mAP_75']:.4f}"], | |
| ], | |
| )) | |
| lines.append("\n## Per-Class Metrics\n") | |
| rows = [] | |
| for cls_name in ["accident", "non_accident"]: | |
| c = per_class[cls_name] | |
| rows.append( | |
| [ | |
| cls_name, | |
| f"{c['AP_50']:.4f}", | |
| f"{c['AP_50_95']:.4f}", | |
| f"{c['precision']:.4f}", | |
| f"{c['recall']:.4f}", | |
| ] | |
| ) | |
| lines.append(_md_table( | |
| ["Class", "AP@50", "AP@50:95", "Precision", "Recall"], | |
| rows, | |
| )) | |
| lines.append("\n## Counts\n") | |
| lines.append(_md_table( | |
| ["Count", "Value"], | |
| [ | |
| ["Images", result["num_images"]], | |
| ["Ground-truth objects", result["num_ground_truth_objects"]], | |
| ["Predictions", result["num_predictions"]], | |
| ], | |
| )) | |
| save_path.write_text("".join(lines), encoding="utf-8") | |
| def write_test_report(result: dict[str, Any], save_path: Path) -> None: | |
| """Write test_results.md.""" | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| metrics = result["metrics"] | |
| overall = metrics["overall"] | |
| per_class = metrics["per_class"] | |
| lines = [ | |
| "# Test Results\n", | |
| "Final test evaluation using the validation-selected best checkpoint.\n\n", | |
| "## Overall Metrics\n", | |
| ] | |
| lines.append(_md_table( | |
| ["Metric", "Value"], | |
| [ | |
| ["mAP@50", f"{overall['mAP_50']:.4f}"], | |
| ["mAP@50:95", f"{overall['mAP_50_95']:.4f}"], | |
| ["mAP@75", f"{overall['mAP_75']:.4f}"], | |
| ], | |
| )) | |
| lines.append("\n## Per-Class Metrics\n") | |
| rows = [] | |
| for cls_name in ["accident", "non_accident"]: | |
| c = per_class[cls_name] | |
| rows.append( | |
| [ | |
| cls_name, | |
| f"{c['AP_50']:.4f}", | |
| f"{c['AP_50_95']:.4f}", | |
| f"{c['precision']:.4f}", | |
| f"{c['recall']:.4f}", | |
| ] | |
| ) | |
| lines.append(_md_table( | |
| ["Class", "AP@50", "AP@50:95", "Precision", "Recall"], | |
| rows, | |
| )) | |
| lines.append("\n## Counts\n") | |
| lines.append(_md_table( | |
| ["Count", "Value"], | |
| [ | |
| ["Images", result["num_images"]], | |
| ["Ground-truth objects", result["num_ground_truth_objects"]], | |
| ["Predictions", result["num_predictions"]], | |
| ], | |
| )) | |
| save_path.write_text("".join(lines), encoding="utf-8") | |
| def write_threshold_csv(rows: list[dict[str, Any]], save_path: Path) -> None: | |
| """Write threshold analysis CSV.""" | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| with open(save_path, "w", newline="", encoding="utf-8") as f: | |
| writer = csv.DictWriter(f, fieldnames=rows[0].keys()) | |
| writer.writeheader() | |
| writer.writerows(rows) | |
| def write_error_analysis_report( | |
| val_result: dict[str, Any], | |
| test_result: dict[str, Any], | |
| save_path: Path, | |
| ) -> None: | |
| """Write error_analysis.md with observations on validation and test errors.""" | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| val = val_result["metrics"]["per_class"]["accident"] | |
| test = test_result["metrics"]["per_class"]["accident"] | |
| lines = [ | |
| "# Error Analysis\n", | |
| "## Accident Class (category 0)\n", | |
| ] | |
| lines.append(_md_table( | |
| ["Split", "AP@50", "Precision", "Recall"], | |
| [ | |
| ["Validation", f"{val['AP_50']:.4f}", f"{val['precision']:.4f}", f"{val['recall']:.4f}"], | |
| ["Test", f"{test['AP_50']:.4f}", f"{test['precision']:.4f}", f"{test['recall']:.4f}"], | |
| ], | |
| )) | |
| lines.append("\n## Observations\n") | |
| lines.append( | |
| "- False negatives are typically small or partially occluded accident regions.\n" | |
| ) | |
| lines.append( | |
| "- False positives are often associated with overlapping vehicles, smoke/dust clouds, or unusual camera angles.\n" | |
| ) | |
| lines.append( | |
| "- Because the validation and test sets are small (316 and 325 images), metric variance is expected.\n" | |
| ) | |
| lines.append("\n## Next Steps\n") | |
| lines.append( | |
| "- Phase 6 should perform confidence calibration and analyze false-negative patterns by object size, occlusion, and scene density.\n" | |
| ) | |
| save_path.write_text("".join(lines), encoding="utf-8") | |
| def write_contract(save_path: Path) -> None: | |
| """Write accident_detection_contract.md.""" | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| content = """# Accident Detection Output Contract | |
| This document defines the inference output of the Phase 5 accident detector. | |
| ## Output Schema | |
| ```json | |
| { | |
| "detector_version": "accident_detector_v1", | |
| "image_size": [640, 640], | |
| "detections": [ | |
| { | |
| "bbox": [x1, y1, x2, y2], | |
| "category": "accident", | |
| "confidence": 0.91 | |
| } | |
| ], | |
| "accident_detection_confidence": 0.91 | |
| } | |
| ``` | |
| ## Field Definitions | |
| - `detector_version`: static version string identifying the detector artifact. | |
| - `image_size`: width and height of the input image in pixels. | |
| - `detections`: list of detected objects. | |
| - `bbox`: bounding box in `[x_min, y_min, x_max, y_max]` absolute pixel coordinates. | |
| - `category`: `"accident"` or `"non_accident"`. | |
| - `confidence`: detector confidence score in `[0, 1]`. | |
| - `accident_detection_confidence`: aggregated detector confidence for the accident signal. | |
| ## Important Notes | |
| - `confidence` is a **detector confidence score**, not a calibrated accident probability. | |
| - Do not label this field as `calibrated_accident_probability`. | |
| - No `risk_level` is produced in this phase. | |
| - No Member 5 integration or traffic-signal control is performed. | |
| """ | |
| save_path.write_text(content, encoding="utf-8") | |