Spaces:
Build error
Build error
File size: 5,698 Bytes
583e46a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | """
Inference predictor module.
Loads a trained model checkpoint and runs predictions.
"""
import sys
import time
from pathlib import Path
from typing import Optional, Union
import torch
import torch.nn.functional as F
from PIL import Image
PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from ml.src.inference.preprocess import preprocess_image
from ml.src.data.breed_metadata import BreedMetadataStore
class CattlePredictor:
"""
End-to-end predictor that loads a model and provides predictions
with breed metadata enrichment.
"""
def __init__(
self,
checkpoint_path: str | Path,
class_names: Optional[list[str]] = None,
metadata_path: Optional[str | Path] = None,
device: Optional[torch.device] = None,
img_size: int = 224,
):
self.img_size = img_size
self.device = device or torch.device('cpu')
# Load checkpoint
checkpoint = torch.load(checkpoint_path, map_location=self.device, weights_only=False)
# Get class names
if class_names:
self.class_names = class_names
elif 'class_names' in checkpoint:
self.class_names = checkpoint['class_names']
else:
raise ValueError("class_names must be provided or stored in checkpoint")
# Load model
self.model = self._load_model(checkpoint)
self.model.eval()
# Load breed metadata
self.metadata_store = None
if metadata_path:
try:
self.metadata_store = BreedMetadataStore(metadata_path)
except Exception as e:
print(f"Warning: Could not load breed metadata: {e}")
def _load_model(self, checkpoint: dict) -> torch.nn.Module:
"""
Load model from checkpoint. Supports both full model and state_dict.
"""
if 'model_state_dict' in checkpoint:
# Need to recreate the model architecture
model_name = checkpoint.get('model_name', 'resnet')
num_classes = len(self.class_names)
model = self._create_model(model_name, num_classes)
model.load_state_dict(checkpoint['model_state_dict'])
elif isinstance(checkpoint, torch.nn.Module):
model = checkpoint
else:
# Assume it's a full model
model = checkpoint
return model.to(self.device)
def _create_model(self, model_name: str, num_classes: int) -> torch.nn.Module:
"""Create model architecture by name."""
if model_name == 'mlp':
from ml.src.models.mlp import CattleMLP
return CattleMLP(num_classes=num_classes)
elif model_name == 'cnn':
from ml.src.models.cnn import CattleCNN
return CattleCNN(num_classes=num_classes)
elif model_name in ('resnet', 'resnet50'):
from ml.src.models.resnet import CattleResNet
return CattleResNet(num_classes=num_classes, pretrained=False, freeze_backbone=False)
elif model_name in ('vit', 'vit_base'):
from ml.src.models.vit import CattleViT
return CattleViT(num_classes=num_classes, pretrained=False, freeze_backbone=False)
else:
raise ValueError(f"Unknown model: {model_name}")
@torch.no_grad()
def predict(
self,
image: Union[str, bytes, Image.Image],
top_k: int = 3,
) -> dict:
"""
Run prediction on a single image.
Args:
image: file path, URL, base64 string, bytes, or PIL Image
top_k: number of top predictions to return
Returns:
dict with predicted_breed, confidence, top_k predictions, breed_info, etc.
"""
start_time = time.time()
# Preprocess
tensor, pil_image = preprocess_image(image, self.img_size)
tensor = tensor.to(self.device)
# Inference
outputs = self.model(tensor)
probabilities = F.softmax(outputs, dim=1).squeeze()
# Get top-k
top_k_probs, top_k_indices = torch.topk(probabilities, min(top_k, len(self.class_names)))
top_k_results = []
for prob, idx in zip(top_k_probs, top_k_indices):
breed_name = self.class_names[idx.item()]
top_k_results.append({
'breed': breed_name,
'confidence': round(prob.item(), 4),
})
# Best prediction
best = top_k_results[0]
result = {
'predicted_breed': best['breed'],
'confidence': best['confidence'],
'top_k': top_k_results,
'inference_time_ms': round((time.time() - start_time) * 1000, 2),
}
# Add breed metadata
if self.metadata_store:
breed_info = self.metadata_store.get_summary_for_prediction(best['breed'])
if breed_info:
result['breed_info'] = breed_info
# Add confidence warning
if best['confidence'] < 0.5:
result['warning'] = (
'Prediction confidence is low. '
'Use a clear side-view image for better results.'
)
elif best['confidence'] < 0.75:
result['warning'] = (
'Prediction confidence is moderate. '
'Use a clear side-view image for better results.'
)
return result
def predict_batch(
self,
images: list[Union[str, bytes, Image.Image]],
top_k: int = 3,
) -> list[dict]:
"""Run predictions on a batch of images."""
return [self.predict(img, top_k) for img in images]
|