import gradio as gr import torch import json from PIL import Image from torchvision import transforms import time import pandas as pd from pathlib import Path import io import base64 from reportlab.lib.pagesizes import letter, A4 from reportlab.lib import colors from reportlab.lib.units import inch from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak, Image as RLImage from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT from datetime import datetime print("ā Packages installed!\n") print("š Creating Gradio Interface...\n") # ==================== LOAD MODEL & METADATA ==================== class BusClassifierInference: def __init__(self, model_path='deployment/bus_classifier_traced.pt', metadata_path='deployment/model_metadata.json'): """Initialize the inference model""" # Load metadata with open(metadata_path, 'r') as f: self.metadata = json.load(f) self.class_names = self.metadata['class_names'] self.device = 'cuda' if torch.cuda.is_available() else 'cpu' print(f"š§ Loading model on {self.device.upper()}...") # Try loading TorchScript first, fallback to PyTorch checkpoint try: self.model = torch.jit.load(model_path, map_location=self.device) print(f"ā TorchScript model loaded from {model_path}") except: print(f"ā ļø TorchScript not found, loading PyTorch checkpoint...") from torchvision import models # Load checkpoint checkpoint = torch.load('deployment/bus_classifier.pth', map_location=self.device) # Recreate model architecture self.model = models.efficientnet_b0(weights=None) num_features = self.model.classifier[1].in_features self.model.classifier[1] = torch.nn.Linear(num_features, len(self.class_names)) # Load weights self.model.load_state_dict(checkpoint['model_state_dict']) self.model = self.model.to(self.device) print(f"ā PyTorch checkpoint loaded") self.model.eval() # Define transform self.transform = transforms.Compose([ transforms.Resize((self.metadata['image_size'], self.metadata['image_size'])), transforms.ToTensor(), transforms.Normalize( mean=self.metadata['normalization']['mean'], std=self.metadata['normalization']['std'] ) ]) print(f"ā Model ready for inference!") print(f"š Classes: {', '.join(self.class_names)}\n") def predict_single(self, image): """Predict class for a single image""" start_time = time.time() # Load image if path provided if isinstance(image, (str, Path)): image = Image.open(image).convert('RGB') elif not isinstance(image, Image.Image): image = Image.fromarray(image).convert('RGB') # Preprocess input_tensor = self.transform(image).unsqueeze(0).to(self.device) # Inference with torch.no_grad(): logits = self.model(input_tensor) probs = torch.softmax(logits, dim=1) pred_class_idx = torch.argmax(probs, dim=1).item() confidence = probs[0][pred_class_idx].item() inference_time = time.time() - start_time # Get all probabilities all_probs = { self.class_names[i]: float(probs[0][i].item()) for i in range(len(self.class_names)) } # Sort by confidence sorted_probs = dict(sorted(all_probs.items(), key=lambda x: x[1], reverse=True)) return { 'predicted_class': self.class_names[pred_class_idx], 'confidence': confidence, 'all_probabilities': sorted_probs, 'inference_time_ms': inference_time * 1000 } def predict_batch(self, images): """Predict for multiple images""" results = [] total_start = time.time() for idx, image in enumerate(images): result = self.predict_single(image) result['image_index'] = idx + 1 results.append(result) total_time = time.time() - total_start return results, total_time # Initialize model print("="*80) predictor = BusClassifierInference() print("="*80) # ==================== PDF GENERATION FUNCTION ==================== def generate_pdf_report(results, images, total_time): """Generate a professional PDF report""" # Create temporary file pdf_filename = f"classification_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" # Create PDF doc = SimpleDocTemplate(pdf_filename, pagesize=letter) story = [] styles = getSampleStyleSheet() # Custom styles title_style = ParagraphStyle( 'CustomTitle', parent=styles['Heading1'], fontSize=24, textColor=colors.HexColor('#667eea'), spaceAfter=30, alignment=TA_CENTER, fontName='Helvetica-Bold' ) heading_style = ParagraphStyle( 'CustomHeading', parent=styles['Heading2'], fontSize=16, textColor=colors.HexColor('#333333'), spaceAfter=12, spaceBefore=12, fontName='Helvetica-Bold' ) # Title story.append(Paragraph("š Bus Component Classification Report", title_style)) story.append(Paragraph(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal'])) story.append(Spacer(1, 0.3*inch)) # Summary Section story.append(Paragraph("š Executive Summary", heading_style)) summary_data = [ ['Metric', 'Value'], ['Total Images Processed', str(len(images))], ['Total Processing Time', f'{total_time:.2f} seconds'], ['Average Time per Image', f'{total_time/len(images)*1000:.2f} ms'], ['Model Used', 'EfficientNet-B0'], ['Model Accuracy', '98.71%'], ['Device', predictor.device.upper()], ] summary_table = Table(summary_data, colWidths=[3*inch, 3*inch]) summary_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#667eea')), ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 12), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.beige), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), ('FONTSIZE', (0, 1), (-1, -1), 10), ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]), ])) story.append(summary_table) story.append(Spacer(1, 0.3*inch)) # Performance Metrics story.append(Paragraph("š Performance Metrics", heading_style)) avg_confidence = sum([r['confidence'] for r in results]) / len(results) high_conf = sum([1 for r in results if r['confidence'] >= 0.95]) medium_conf = sum([1 for r in results if 0.80 <= r['confidence'] < 0.95]) low_conf = sum([1 for r in results if r['confidence'] < 0.80]) perf_data = [ ['Performance Metric', 'Value', 'Percentage'], ['Average Confidence', f'{avg_confidence*100:.2f}%', '-'], ['High Confidence (ā„95%)', str(high_conf), f'{high_conf/len(images)*100:.1f}%'], ['Medium Confidence (80-95%)', str(medium_conf), f'{medium_conf/len(images)*100:.1f}%'], ['Low Confidence (<80%)', str(low_conf), f'{low_conf/len(images)*100:.1f}%'], ] perf_table = Table(perf_data, colWidths=[2.5*inch, 1.5*inch, 1.5*inch]) perf_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4CAF50')), ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 11), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]), ])) story.append(perf_table) story.append(Spacer(1, 0.3*inch)) # Class Distribution story.append(Paragraph("š¦ Class Distribution", heading_style)) class_counts = {} for result in results: pred = result['predicted_class'] class_counts[pred] = class_counts.get(pred, 0) + 1 dist_data = [['Class Name', 'Count', 'Percentage']] for class_name, count in sorted(class_counts.items(), key=lambda x: x[1], reverse=True): dist_data.append([class_name, str(count), f'{count/len(images)*100:.1f}%']) dist_table = Table(dist_data, colWidths=[3*inch, 1.5*inch, 1.5*inch]) dist_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2196F3')), ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 11), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]), ])) story.append(dist_table) story.append(PageBreak()) # Detailed Results story.append(Paragraph("š Detailed Classification Results", heading_style)) story.append(Spacer(1, 0.2*inch)) # Create detailed table detail_data = [['#', 'Predicted Class', 'Confidence', 'Time (ms)', '2nd Best', '2nd Conf']] for result in results: second_best = list(result['all_probabilities'].keys())[1] second_conf = list(result['all_probabilities'].values())[1] detail_data.append([ str(result['image_index']), result['predicted_class'], f"{result['confidence']*100:.2f}%", f"{result['inference_time_ms']:.2f}", second_best, f"{second_conf*100:.2f}%" ]) detail_table = Table(detail_data, colWidths=[0.5*inch, 1.8*inch, 1*inch, 0.9*inch, 1.8*inch, 1*inch]) detail_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#764ba2')), ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 9), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('FONTSIZE', (0, 1), (-1, -1), 8), ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]), ])) story.append(detail_table) story.append(Spacer(1, 0.3*inch)) # Footer story.append(Spacer(1, 0.5*inch)) footer_style = ParagraphStyle( 'Footer', parent=styles['Normal'], fontSize=9, textColor=colors.grey, alignment=TA_CENTER ) story.append(Paragraph("Bus Component Classification System v1.0 | Powered by EfficientNet-B0", footer_style)) story.append(Paragraph("This report is auto-generated and contains AI predictions.", footer_style)) # Build PDF doc.build(story) print(f"ā PDF Report generated: {pdf_filename}") return pdf_filename # ==================== GRADIO INTERFACE FUNCTIONS ==================== def predict_images(images): """Main prediction function for Gradio interface""" if images is None or len(images) == 0: return "
EfficientNet-B0 | Accuracy: 98.71% | Real-time Classification
Click the button below to select images (JPG, PNG | Max: 50 images)