| |
| """ |
| Inference script for trained models |
| """ |
| import argparse |
| import sys |
| from pathlib import Path |
| from typing import Dict, List, Optional, Union, Tuple |
| import json |
|
|
| sys.path.append(str(Path(__file__).parent.parent)) |
|
|
| import torch |
| import torch.nn.functional as F |
| from PIL import Image |
| import numpy as np |
|
|
| from src.models.model_factory import ModelFactory |
| from src.data.transforms import get_transforms_for_model |
| from src.utils.helpers import get_device |
|
|
|
|
| class ModelInference: |
| """Class for model inference""" |
| |
| def __init__( |
| self, |
| checkpoint_path: str, |
| device: Optional[torch.device] = None, |
| multi_task: bool = False |
| ): |
| """ |
| Initialize inference engine |
| |
| Args: |
| checkpoint_path: Path to model checkpoint |
| device: Device to run inference on |
| multi_task: Whether the model is multi-task |
| """ |
| self.checkpoint_path = Path(checkpoint_path) |
| self.device = device or get_device() |
| self.multi_task = multi_task |
| |
| |
| try: |
| self.checkpoint = torch.load(self.checkpoint_path, map_location=self.device, weights_only=True) |
| except Exception as e: |
| print(f"Trying with weights_only=False due to: {e}") |
| self.checkpoint = torch.load(self.checkpoint_path, map_location=self.device, weights_only=False) |
| self.config = self.checkpoint.get('config', {}) |
| |
| |
| if 'multi_task' in self.checkpoint: |
| self.multi_task = self.checkpoint['multi_task'] |
| |
| |
| self._create_model() |
| |
| |
| self.transform = get_transforms_for_model( |
| self.config.get('model_name', 'efficientnet-b0'), |
| is_training=False |
| ) |
| |
| |
| if 'class_names' in self.checkpoint: |
| |
| if self.multi_task: |
| self.decades = self.checkpoint['class_names'].get('decade', ['1960s', '1970s', '1980s', '1990s', '2000s']) |
| self.clusters = self.checkpoint['class_names'].get('cluster', [0, 1, 2, 3, 4]) |
| self.devices = self.checkpoint['class_names'].get('device', ['calculator', 'phone']) |
| else: |
| self.decades = self.checkpoint['class_names'] |
| if not isinstance(self.decades, list): |
| self.decades = ['1960s', '1970s', '1980s', '1990s', '2000s'] |
| self.clusters = [0, 1, 2, 3, 4] |
| self.devices = ['calculator', 'phone'] |
| else: |
| |
| self.decades = ['1960s', '1970s', '1980s', '1990s', '2000s'] |
| self.clusters = [0, 1, 2, 3, 4] |
| self.devices = ['calculator', 'phone'] |
| |
| print(f"Model configured for {len(self.decades)} decade classes") |
| print(f"Decade labels: {self.decades}") |
| if self.multi_task: |
| print(f"Cluster labels: {self.clusters}") |
| print(f"Device labels: {self.devices}") |
| |
| def _create_model(self): |
| """Create and load model""" |
| model_name = self.config.get('model_name', 'efficientnet-b0') |
| |
| if self.multi_task: |
| |
| num_classes = { |
| 'decade': self.config.get('num_decade_classes', 5), |
| 'cluster': self.config.get('num_cluster_classes', 5), |
| 'device': self.config.get('num_device_classes', 2) |
| } |
| self.model = ModelFactory.create_model( |
| model_name, |
| num_classes=num_classes, |
| multi_task=True, |
| multitask_config={ |
| 'hidden_dim': self.config.get('multitask_hidden_dim', 512), |
| 'dropout_rate': self.config.get('multitask_dropout', 0.3), |
| 'num_device_classes': self.config.get('num_device_classes', 2) |
| }, |
| pretrained=False |
| ) |
| else: |
| |
| num_classes = self.config.get('num_classes', 5) |
| self.model = ModelFactory.create_model( |
| model_name, |
| num_classes=num_classes, |
| pretrained=False |
| ) |
| |
| |
| self.model.load_state_dict(self.checkpoint['model_state_dict']) |
| self.model.to(self.device) |
| self.model.eval() |
| |
| print(f"Loaded {model_name} model from {self.checkpoint_path}") |
| print(f"Multi-task mode: {self.multi_task}") |
| |
| def predict_image(self, image_path: Union[str, Path]) -> Dict: |
| """ |
| Predict on a single image |
| |
| Args: |
| image_path: Path to image file |
| |
| Returns: |
| Dictionary with predictions |
| """ |
| |
| image = Image.open(image_path).convert('RGB') |
| image_tensor = self.transform(image).unsqueeze(0).to(self.device) |
| |
| |
| with torch.no_grad(): |
| output = self.model(image_tensor) |
| |
| if self.multi_task: |
| |
| decade_probs = F.softmax(output['decade'], dim=1) |
| cluster_probs = F.softmax(output['cluster'], dim=1) |
| |
| |
| if 'device' in output: |
| device_probs = F.softmax(output['device'], dim=1) |
| device_prob, device_idx = device_probs.max(1) |
| else: |
| device_probs = None |
| device_prob = None |
| device_idx = None |
| |
| |
| decade_prob, decade_idx = decade_probs.max(1) |
| cluster_prob, cluster_idx = cluster_probs.max(1) |
| |
| |
| decade_top3_probs, decade_top3_idx = decade_probs.topk(3, dim=1) |
| cluster_top3_probs, cluster_top3_idx = cluster_probs.topk(3, dim=1) |
| |
| result = { |
| 'decade': { |
| 'prediction': self.decades[decade_idx.item()], |
| 'confidence': decade_prob.item(), |
| 'top3': [ |
| { |
| 'class': self.decades[idx], |
| 'confidence': prob |
| } |
| for idx, prob in zip( |
| decade_top3_idx[0].tolist(), |
| decade_top3_probs[0].tolist() |
| ) |
| ] |
| }, |
| 'cluster': { |
| 'prediction': self.clusters[cluster_idx.item()], |
| 'confidence': cluster_prob.item(), |
| 'top3': [ |
| { |
| 'class': self.clusters[idx], |
| 'confidence': prob |
| } |
| for idx, prob in zip( |
| cluster_top3_idx[0].tolist(), |
| cluster_top3_probs[0].tolist() |
| ) |
| ] |
| } |
| } |
| |
| |
| if device_probs is not None: |
| result['device'] = { |
| 'prediction': self.devices[device_idx.item()], |
| 'confidence': device_prob.item() |
| } |
| else: |
| |
| probs = F.softmax(output, dim=1) |
| prob, idx = probs.max(1) |
| |
| |
| top3_probs, top3_idx = probs.topk(3, dim=1) |
| |
| result = { |
| 'decade': { |
| 'prediction': self.decades[idx.item()], |
| 'confidence': prob.item(), |
| 'top3': [ |
| { |
| 'class': self.decades[i], |
| 'confidence': p |
| } |
| for i, p in zip(top3_idx[0].tolist(), top3_probs[0].tolist()) |
| ], |
| 'all_probabilities': { |
| self.decades[i]: p for i, p in enumerate(probs[0].tolist()) |
| } |
| } |
| } |
| |
| return result |
| |
| def predict_batch(self, image_paths: List[Union[str, Path]]) -> List[Dict]: |
| """ |
| Predict on multiple images |
| |
| Args: |
| image_paths: List of image paths |
| |
| Returns: |
| List of prediction dictionaries |
| """ |
| results = [] |
| |
| for image_path in image_paths: |
| try: |
| result = self.predict_image(image_path) |
| result['image_path'] = str(image_path) |
| result['status'] = 'success' |
| results.append(result) |
| except Exception as e: |
| results.append({ |
| 'image_path': str(image_path), |
| 'status': 'error', |
| 'error': str(e) |
| }) |
| |
| return results |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser(description='Run inference on images') |
| parser.add_argument('checkpoint', type=str, help='Path to model checkpoint') |
| parser.add_argument('images', nargs='+', help='Image paths to predict on') |
| parser.add_argument('--device', type=str, default=None, |
| help='Device to use (cpu, cuda, cuda:0, etc.)') |
| parser.add_argument('--output', type=str, default=None, |
| help='Output JSON file for results') |
| parser.add_argument('--multi-task', action='store_true', |
| help='Use multi-task model') |
| |
| args = parser.parse_args() |
| |
| |
| inference = ModelInference( |
| checkpoint_path=args.checkpoint, |
| device=get_device(args.device), |
| multi_task=args.multi_task |
| ) |
| |
| |
| if len(args.images) == 1 and Path(args.images[0]).is_dir(): |
| |
| image_dir = Path(args.images[0]) |
| image_paths = list(image_dir.glob('*.jpg')) + \ |
| list(image_dir.glob('*.jpeg')) + \ |
| list(image_dir.glob('*.png')) |
| print(f"Found {len(image_paths)} images in {image_dir}") |
| else: |
| |
| image_paths = [Path(p) for p in args.images] |
| |
| |
| results = inference.predict_batch(image_paths) |
| |
| |
| for result in results: |
| print(f"\nImage: {result['image_path']}") |
| if result['status'] == 'success': |
| if 'decade' in result: |
| decade_info = result['decade'] |
| print(f" Decade: {decade_info['prediction']} " |
| f"(confidence: {decade_info['confidence']:.2%})") |
| print(" Top 3 decades:") |
| for i, pred in enumerate(decade_info['top3'], 1): |
| print(f" {i}. {pred['class']}: {pred['confidence']:.2%}") |
| |
| if 'cluster' in result: |
| cluster_info = result['cluster'] |
| print(f" Cluster: {cluster_info['prediction']} " |
| f"(confidence: {cluster_info['confidence']:.2%})") |
| else: |
| print(f" Error: {result['error']}") |
| |
| |
| if args.output: |
| with open(args.output, 'w') as f: |
| json.dump(results, f, indent=2) |
| print(f"\nResults saved to {args.output}") |
|
|
|
|
| if __name__ == '__main__': |
| main() |