jitendra205saini
Production-ready PyTorch image classification pipeline
3c4611e
Raw History Blame Contribute Delete
4.35 kB
#!/usr/bin/env python3
"""
Advanced command-line image classifier.
Usage
-----
python predict.py path/to/image.jpg
python predict.py path/to/image.jpg --top_k 10
python predict.py path/to/image.jpg --model efficientnet_b0
Prints a clean, formatted ASCII table of the top-k predictions.
"""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
from typing import List, Tuple
import torch
import torch.nn.functional as F
from PIL import Image
from torchvision import models, transforms
def load_model(model_name: str) -> Tuple[torch.nn.Module, transforms.Compose, List[str]]:
"""Load a pretrained model with its canonical preprocessing and labels."""
model_name = model_name.lower()
if model_name == "efficientnet_b0":
weights = models.EfficientNet_B0_Weights.IMAGENET1K_V1
model = models.efficientnet_b0(weights=weights)
elif model_name == "mobilenet_v3_large":
weights = models.MobileNet_V3_Large_Weights.IMAGENET1K_V2
model = models.mobilenet_v3_large(weights=weights)
else:
raise ValueError(
f"Unknown model '{model_name}'. "
"Choose from: mobilenet_v3_large, efficientnet_b0"
)
model.eval()
return model, weights.transforms(), list(weights.meta["categories"])
def classify(
image_path: Path, model_name: str, top_k: int
) -> Tuple[List[Tuple[str, float]], float]:
"""Return (list of (label, confidence), inference_ms)."""
model, preprocess, categories = load_model(model_name)
image = Image.open(image_path).convert("RGB")
tensor = preprocess(image).unsqueeze(0)
t0 = time.perf_counter()
with torch.inference_mode():
probs = F.softmax(model(tensor), dim=1)[0]
inference_ms = (time.perf_counter() - t0) * 1000
top_k = max(1, min(top_k, probs.numel()))
confidences, indices = torch.topk(probs, top_k)
predictions = [
(categories[idx], float(conf))
for conf, idx in zip(confidences.tolist(), indices.tolist())
]
return predictions, inference_ms
def render_table(predictions: List[Tuple[str, float]]) -> str:
"""Render predictions as a bordered ASCII table with confidence bars."""
rank_w, label_w, conf_w, bar_w = 4, 28, 10, 22
def row(rank: str, label: str, conf: str, bar: str) -> str:
return (
f"| {rank:<{rank_w}} | {label:<{label_w}} | "
f"{conf:<{conf_w}} | {bar:<{bar_w}} |"
)
border = (
"+" + "-" * (rank_w + 2) + "+" + "-" * (label_w + 2)
+ "+" + "-" * (conf_w + 2) + "+" + "-" * (bar_w + 2) + "+"
)
lines = [border, row("#", "Class", "Conf.", "Confidence"), border]
for i, (label, conf) in enumerate(predictions, start=1):
pct = conf * 100
filled = int(round((bar_w) * conf))
bar = "#" * filled + "." * (bar_w - filled)
lines.append(row(str(i), label[:label_w], f"{pct:6.2f}%", bar))
lines.append(border)
return "\n".join(lines)
def main(argv: List[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Classify an image from the command line using a pretrained CNN.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("image", type=Path, help="Path to the image file.")
parser.add_argument(
"--top_k", type=int, default=5, help="Number of top predictions to display."
)
parser.add_argument(
"--model",
default="mobilenet_v3_large",
choices=["mobilenet_v3_large", "efficientnet_b0"],
help="Model architecture to use.",
)
args = parser.parse_args(argv)
if not args.image.exists():
print(f"Error: file not found -> {args.image}", file=sys.stderr)
return 1
print(f"\n Model : {args.model}")
print(f" Image : {args.image}\n")
try:
predictions, inference_ms = classify(args.image, args.model, args.top_k)
except Exception as exc: # noqa: BLE001
print(f"Error during inference: {exc}", file=sys.stderr)
return 1
print(render_table(predictions))
print(f"\n Top prediction : {predictions[0][0]} ({predictions[0][1] * 100:.2f}%)")
print(f" Inference time : {inference_ms:.1f} ms\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())