Leaf / core /classifier.py
thienphuc12339's picture
Deploy Leaf Disease Real-Time API
60dd9a9
Raw
History Blame Contribute Delete
2.98 kB
from __future__ import annotations
from pathlib import Path
import cv2
import numpy as np
import torch
import torch.nn as nn
from torchvision import models, transforms
class DiseaseClassifier:
def __init__(self, checkpoint_path: Path, device: str = "auto", image_size: int = 320) -> None:
self.device = self._resolve_device(device)
self.checkpoint_path = Path(checkpoint_path)
checkpoint = torch.load(
self.checkpoint_path,
map_location=self.device,
weights_only=False,
)
self.model_name = checkpoint.get("model_name", "efficientnet_v2_s")
self.class_names = list(checkpoint["class_names"])
self.image_size = int(checkpoint.get("img_size", image_size))
self.model = self._build_model(self.model_name, len(self.class_names))
self.model.load_state_dict(checkpoint["model_state"])
self.model.to(self.device)
self.model.eval()
self.transform = transforms.Compose(
[
transforms.ToPILImage(),
transforms.Resize(int(self.image_size * 1.12)),
transforms.CenterCrop(self.image_size),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
),
]
)
@staticmethod
def _resolve_device(device: str) -> str:
if device == "auto":
return "cuda" if torch.cuda.is_available() else "cpu"
return device
@staticmethod
def _build_model(model_name: str, num_classes: int) -> nn.Module:
if model_name == "efficientnet_v2_s":
model = models.efficientnet_v2_s(weights=None)
in_features = model.classifier[1].in_features
model.classifier[1] = nn.Linear(in_features, num_classes)
return model
if model_name == "convnext_tiny":
model = models.convnext_tiny(weights=None)
in_features = model.classifier[2].in_features
model.classifier[2] = nn.Linear(in_features, num_classes)
return model
raise ValueError(f"Unsupported classifier model: {model_name}")
@torch.inference_mode()
def predict(self, crop_bgr: np.ndarray) -> dict:
if crop_bgr.size == 0:
raise ValueError("Empty crop passed to disease classifier")
crop_rgb = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB)
tensor = self.transform(crop_rgb).unsqueeze(0).to(self.device)
logits = self.model(tensor)
probs = torch.softmax(logits, dim=1)[0].detach().cpu().numpy()
idx = int(np.argmax(probs))
return {
"label": self.class_names[idx],
"confidence": float(probs[idx]),
"probabilities": {
class_name: float(probs[i])
for i, class_name in enumerate(self.class_names)
},
}