mfft-api / api /model_server.py
MohsinEli's picture
Deploy MFFT multi-model detection API
abeae79 verified
Raw
History Blame Contribute Delete
3.6 kB
import time
import torch
import torch.nn.functional as F
import numpy as np
from PIL import Image
from pathlib import Path
from typing import Optional, Dict, Any
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "model" / "src"))
from model import MFFTWithExplainability, build_mfft
class ModelServer:
"""
Wraps the MFFT model for production inference.
Handles model loading, preprocessing, prediction, and warmup.
"""
def __init__(self, checkpoint_path: Optional[str] = None, variant: str = "base",
image_size: int = 384):
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.variant = variant
self.image_size = image_size
print(f"[ModelServer] Device: {self.device} | Variant: {variant} | Size: {image_size}")
self.model = build_mfft(variant)
self.model = self.model.to(self.device)
self.is_loaded = False
if checkpoint_path and Path(checkpoint_path).exists():
self.load_checkpoint(checkpoint_path)
self.is_loaded = True
print(f"[ModelServer] Model loaded from {checkpoint_path}")
else:
print(f"[ModelServer] No checkpoint found at {checkpoint_path}")
print(f"[ModelServer] Running with untrained weights (random initialization)")
self.warmup()
def load_checkpoint(self, path: str):
state = torch.load(path, map_location=self.device, weights_only=True)
if "model_state_dict" in state:
self.model.load_state_dict(state["model_state_dict"])
else:
self.model.load_state_dict(state)
self.is_loaded = True
def warmup(self, n_warmup: int = 3):
dummy = torch.randn(1, 3, self.image_size, self.image_size).to(self.device)
self.model.eval()
with torch.no_grad():
for _ in range(n_warmup):
_ = self.model(dummy)
print(f"[ModelServer] Warmup complete ({n_warmup} iterations)")
@torch.no_grad()
def predict(self, image: Image.Image) -> Dict[str, Any]:
start = time.time()
input_tensor = self._preprocess(image)
logits, heatmaps = self.model(input_tensor, return_heatmap=True)
probs = F.softmax(logits, dim=-1)
pred = torch.argmax(probs, dim=-1).item()
bands = self.model.decomposer(input_tensor)
freq_magnitudes = [band.abs().mean().item() for band in bands]
elapsed = (time.time() - start) * 1000
result = {
"prediction": pred,
"real_prob": probs[0, 0].item(),
"ai_prob": probs[0, 1].item(),
"confidence": probs.max(dim=-1).values.item(),
"heatmaps": heatmaps,
"frequency_band_contributions": {
"low_frequency": freq_magnitudes[0],
"mid_frequency": freq_magnitudes[1],
"high_frequency": freq_magnitudes[2],
},
"processing_time_ms": elapsed,
}
return result
def _preprocess(self, image: Image.Image) -> torch.Tensor:
image = image.convert("RGB").resize(
(self.image_size, self.image_size), Image.Resampling.LANCZOS)
img_array = np.array(image, dtype=np.float32) / 255.0
mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
img_array = ((img_array - mean) / std).astype(np.float32)
tensor = torch.from_numpy(img_array).permute(2, 0, 1).unsqueeze(0)
return tensor.to(self.device)