File size: 5,801 Bytes
81b4246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
CarDentIQ β€” Model Evaluation
Developer: Saksham Pathak (github.com/parthmax2)

Runs YOLO validation on a chosen split and reports per-class metrics,
overall mAP, and an optional inference speed benchmark.

Usage (run from project root):
  python scripts/evaluate.py                          # test split, best.pt
  python scripts/evaluate.py --model runs/phase2_full/weights/best.pt
  python scripts/evaluate.py --split val
  python scripts/evaluate.py --save-images            # save annotated predictions
  python scripts/evaluate.py --speed                  # add latency benchmark
  python scripts/evaluate.py --conf 0.25 --iou 0.5   # custom NMS thresholds
"""

import argparse
import time
from pathlib import Path

from ultralytics import YOLO

# ── project root ─────────────────────────────────────────────────
ROOT          = Path(__file__).resolve().parent.parent
DATA_YAML     = str(ROOT / "configs" / "data.yaml")
DEFAULT_MODEL = str(ROOT / "best.pt")
RUNS_DIR      = str(ROOT / "runs")

CLASS_NAMES = [
    "no_damage", "lost_parts", "torn", "dent",
    "paint_scratch", "hole", "broken_glass", "broken_lamp",
]


# ─────────────────────────────────────────────
def run_validation(
    model_path: str,
    split: str,
    conf: float,
    iou: float,
    save_images: bool,
):
    if not Path(model_path).exists():
        raise FileNotFoundError(f"Weights not found: {model_path}")
    if not Path(DATA_YAML).exists():
        raise FileNotFoundError(f"Dataset config not found: {DATA_YAML}")

    print(f"\n{'='*60}")
    print(f"  Model  : {model_path}")
    print(f"  Split  : {split}")
    print(f"  Conf   : {conf}   IoU: {iou}")
    print(f"{'='*60}")

    model   = YOLO(model_path)
    metrics = model.val(
        data        = DATA_YAML,
        split       = split,
        conf        = conf,
        iou         = iou,
        save        = save_images,
        save_txt    = False,
        save_json   = False,
        plots       = True,
        project     = RUNS_DIR,
        name        = f"eval_{split}",
        exist_ok    = True,
        verbose     = True,
    )

    _print_results(metrics)
    return metrics


# ─────────────────────────────────────────────
def _print_results(metrics) -> None:
    box = metrics.box

    print(f"\n{'='*60}")
    print("  OVERALL METRICS")
    print(f"{'='*60}")
    print(f"  mAP @ 0.5        : {box.map50:.4f}")
    print(f"  mAP @ 0.5:0.95   : {box.map:.4f}")
    print(f"  Precision (mean) : {box.mp:.4f}")
    print(f"  Recall    (mean) : {box.mr:.4f}")

    print(f"\n{'='*60}")
    print("  PER-CLASS METRICS")
    print(f"{'='*60}")
    header = f"  {'Class':<20} {'AP@0.5':>8} {'mAP':>8} {'P':>8} {'R':>8}"
    print(header)
    print("  " + "-" * (len(header) - 2))

    for i, name in enumerate(CLASS_NAMES):
        ap50 = box.ap50[i] if i < len(box.ap50) else float("nan")
        mAP  = box.maps[i] if i < len(box.maps) else float("nan")
        p    = box.p[i]    if i < len(box.p)    else float("nan")
        r    = box.r[i]    if i < len(box.r)    else float("nan")
        print(f"  {name:<20} {ap50:>8.4f} {mAP:>8.4f} {p:>8.4f} {r:>8.4f}")

    print(f"{'='*60}")
    print(f"  Confusion matrix and plots saved to: {RUNS_DIR}/eval_*/\n")


# ─────────────────────────────────────────────
def speed_benchmark(model_path: str, n: int = 100) -> None:
    import torch
    from PIL import Image

    model = YOLO(model_path)
    dummy = Image.new("RGB", (640, 640), color=(128, 128, 128))

    for _ in range(10):              # warmup
        model(dummy, verbose=False)

    t0 = time.perf_counter()
    for _ in range(n):
        model(dummy, verbose=False)
    elapsed = time.perf_counter() - t0

    device = "CUDA" if torch.cuda.is_available() else "CPU"
    print(f"\n[speed] {n} Γ— 640Γ—640 inference on {device}")
    print(f"  Avg latency : {elapsed / n * 1000:.1f} ms / image")
    print(f"  Throughput  : {n / elapsed:.1f} FPS")


# ─────────────────────────────────────────────
def main() -> None:
    parser = argparse.ArgumentParser(
        description="Evaluate a car-damage YOLO model",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    parser.add_argument("--model",       default=DEFAULT_MODEL,
                        help="Path to model weights (.pt)")
    parser.add_argument("--split",       default="test",
                        choices=["train", "val", "test"],
                        help="Dataset split to evaluate on")
    parser.add_argument("--conf",        type=float, default=0.001,
                        help="Confidence threshold (use 0.001 for full mAP curve)")
    parser.add_argument("--iou",         type=float, default=0.6,
                        help="IoU threshold for NMS")
    parser.add_argument("--save-images", action="store_true",
                        help="Save annotated prediction images")
    parser.add_argument("--speed",       action="store_true",
                        help="Run inference speed benchmark after evaluation")
    args = parser.parse_args()

    run_validation(
        model_path  = args.model,
        split       = args.split,
        conf        = args.conf,
        iou         = args.iou,
        save_images = args.save_images,
    )

    if args.speed:
        speed_benchmark(args.model)


if __name__ == "__main__":
    main()