from __future__ import annotations import argparse import json from pathlib import Path from typing import Dict, List from PIL import Image from sklearn.metrics import accuracy_score, classification_report from src.inference import ClipPredictor, CustomModelPredictor, OpenAIVisionPredictor def iter_test_images(data_dir: Path, labels: List[str]) -> List[Dict[str, str]]: items: List[Dict[str, str]] = [] test_root = data_dir / "test" for label in labels: class_dir = test_root / label if not class_dir.exists(): continue for p in class_dir.iterdir(): if p.is_file() and p.suffix.lower() in {".jpg", ".jpeg", ".png", ".webp"}: items.append({"path": str(p), "label": label}) return items def evaluate_custom(custom: CustomModelPredictor, samples: List[Dict[str, str]]) -> Dict[str, object]: y_true = [] y_pred = [] for item in samples: image = Image.open(item["path"]) pred = custom.predict(image) y_true.append(item["label"]) y_pred.append(pred["top_prediction"]["label"]) acc = accuracy_score(y_true, y_pred) report = classification_report(y_true, y_pred, output_dict=True, zero_division=0) return {"accuracy": float(acc), "classification_report": report} def evaluate_clip(clip: ClipPredictor, samples: List[Dict[str, str]]) -> Dict[str, object]: if not clip.available(): return {"available": False, "error": "Could not load CLIP model."} y_true = [] y_pred = [] for item in samples: image = Image.open(item["path"]) pred = clip.predict(image) y_true.append(item["label"]) y_pred.append(pred["top_prediction"]["label"]) acc = accuracy_score(y_true, y_pred) report = classification_report(y_true, y_pred, output_dict=True, zero_division=0) return {"available": True, "accuracy": float(acc), "classification_report": report} def evaluate_openai(openai_model: OpenAIVisionPredictor, samples: List[Dict[str, str]], max_samples: int) -> Dict[str, object]: if not openai_model.available(): return {"available": False, "error": "OPENAI_API_KEY missing."} subset = samples[:max_samples] y_true = [] y_pred = [] for item in subset: image = Image.open(item["path"]) pred = openai_model.predict(image) y_true.append(item["label"]) y_pred.append(pred["top_prediction"]["label"]) acc = accuracy_score(y_true, y_pred) report = classification_report(y_true, y_pred, output_dict=True, zero_division=0) return { "available": True, "evaluated_samples": len(subset), "accuracy": float(acc), "classification_report": report, } def main() -> None: parser = argparse.ArgumentParser(description="Compare custom model vs CLIP vs OpenAI Vision.") parser.add_argument("--data-dir", default="data/pokemon") parser.add_argument("--model-path", default="models/custom_resnet18.pth") parser.add_argument("--output", default="reports/model_comparison.json") parser.add_argument("--openai-max-samples", type=int, default=24) args = parser.parse_args() data_dir = Path(args.data_dir) output_path = Path(args.output) output_path.parent.mkdir(parents=True, exist_ok=True) custom = CustomModelPredictor(args.model_path) if not custom.available(): raise FileNotFoundError( "Custom model checkpoint not found. Train model first with src/train_custom_model.py" ) labels = custom.labels samples = iter_test_images(data_dir, labels) clip = ClipPredictor(labels) openai_model = OpenAIVisionPredictor(labels) result = { "dataset": str(data_dir), "num_test_samples": len(samples), "labels": labels, "custom_model": evaluate_custom(custom, samples), "clip_model": evaluate_clip(clip, samples), "openai_model": evaluate_openai(openai_model, samples, args.openai_max_samples), } output_path.write_text(json.dumps(result, indent=2), encoding="utf-8") print(f"Saved comparison report to: {output_path}") if __name__ == "__main__": main()