cactus-classifier / inference.py
jressler's picture
Upload cactus classifier model
591061a verified
Raw History Blame Contribute Delete
2.09 kB
"""Load and run the exported ConvNeXt Small model with PyTorch and Torchvision."""
from __future__ import annotations
import json
from pathlib import Path
import torch
from PIL import Image
from torchvision import models, transforms
def load_model(model_dir: str | Path, device: str = "auto"):
"""Load model weights and the preprocessing parameters from an export folder."""
model_dir = Path(model_dir)
config = json.loads((model_dir / "config.json").read_text(encoding="utf-8"))
if config["model_name"] != "convnext_small":
raise ValueError(f"Unsupported model architecture: {config['model_name']}")
if device == "auto":
if torch.cuda.is_available():
device = "cuda"
elif torch.backends.mps.is_available():
device = "mps"
else:
device = "cpu"
target = torch.device(device)
model = models.convnext_small(weights=None)
model.classifier[-1] = torch.nn.Linear(
model.classifier[-1].in_features, config["num_labels"],
)
state_dict = torch.load(
model_dir / config["weights_file"], map_location="cpu", weights_only=True,
)
model.load_state_dict(state_dict)
model.to(target).eval()
preprocess = transforms.Compose([
transforms.Resize(config["resize_size"]),
transforms.CenterCrop(config["image_size"]),
transforms.ToTensor(),
transforms.Normalize(mean=config["image_mean"], std=config["image_std"]),
])
return model, preprocess, config["class_names"], target
@torch.inference_mode()
def predict(image: Image.Image, model, preprocess, class_names: list[str], device) -> list[dict]:
"""Return all class scores, ordered from highest to lowest."""
tensor = preprocess(image.convert("RGB")).unsqueeze(0).to(device)
probabilities = torch.softmax(model(tensor), dim=1)[0].cpu().tolist()
return sorted(
({"class_name": name, "probability": float(probabilities[index])}
for index, name in enumerate(class_names)),
key=lambda score: score["probability"], reverse=True,
)