CarDentIQ / scripts /evaluate.py
parthmax24's picture
added the latest updates
81b4246
Raw
History Blame Contribute Delete
5.8 kB
"""
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()