Asadrizvi64's picture
Electrical Outlets diagnostic pipeline v1.0
5666923
"""
Image classifier for Electrical Outlets. EfficientNet-B0 backbone + MLP head.
FINAL v5: 5 classes (no GFCI).
"""
from pathlib import Path
from typing import Dict, Any, Optional
import json
import torch
import torch.nn as nn
from torchvision import models
class ElectricalOutletsImageModel(nn.Module):
def __init__(
self,
num_classes: int = 5,
label_mapping_path: Optional[Path] = None,
pretrained: bool = True,
head_hidden: int = 256,
head_dropout: float = 0.4,
):
super().__init__()
self.num_classes = num_classes
self.backbone = models.efficientnet_b0(
weights=models.EfficientNet_B0_Weights.IMAGENET1K_V1 if pretrained else None
)
in_features = self.backbone.classifier[1].in_features # 1280
self.backbone.classifier = nn.Identity()
self.head = nn.Sequential(
nn.Dropout(head_dropout),
nn.Linear(in_features, head_hidden),
nn.ReLU(),
nn.Dropout(head_dropout * 0.5),
nn.Linear(head_hidden, num_classes),
)
self.idx_to_issue_type = None
self.idx_to_severity = None
if label_mapping_path and Path(label_mapping_path).exists():
with open(label_mapping_path) as f:
lm = json.load(f)
self.idx_to_issue_type = lm["image"]["idx_to_issue_type"]
self.idx_to_severity = lm["image"]["idx_to_severity"]
def forward(self, x: torch.Tensor) -> torch.Tensor:
features = self.backbone(x)
return self.head(features)
def predict_to_schema(self, logits: torch.Tensor) -> Dict[str, Any]:
probs = torch.softmax(logits, dim=-1)
if logits.dim() == 1:
probs = probs.unsqueeze(0)
conf, pred = probs.max(dim=-1)
pred = pred.item() if pred.numel() == 1 else pred
conf = conf.item() if conf.numel() == 1 else conf
issue_type = (self.idx_to_issue_type or ["unknown"] * self.num_classes)[pred]
severity = (self.idx_to_severity or ["medium"] * self.num_classes)[pred]
result = "normal" if issue_type == "normal" else "issue_detected"
return {
"result": result,
"issue_type": issue_type,
"severity": severity,
"confidence": float(conf),
"class_idx": int(pred),
}