import sys from pathlib import Path import numpy as np import requests from io import BytesIO from PIL import Image from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from notifications.serializers import ImageClassifierSerializer, ClassificationResultSerializer import torch import torch.nn as nn from torchvision import transforms import tensorflow as tf # PATHS BASE_DIR = Path(__file__).resolve().parent.parent PYTORCH_PATH = BASE_DIR / "models" / "pytorch_model.pth" TENSORFLOW_PATH = BASE_DIR / "models" / "model_best.keras" CLASSES = ["buildings", "forest", "glacier", "mountain", "sea", "street"] CONFIDENCE_THRESHOLD = 0.6 # PYTORCH MODEL class CNN(nn.Module): def __init__(self, num_classes=6): super().__init__() self.block1 = self._block(3, 32) self.block2 = self._block(32, 64) self.block3 = self._block(64, 128) self.block4 = self._block(128, 256) self.gap = nn.AdaptiveAvgPool2d(1) self.fc1 = nn.Linear(256, 128) self.fc2 = nn.Linear(128, num_classes) self.dropout = nn.Dropout(0.5) def _block(self, in_c, out_c): return nn.Sequential( nn.Conv2d(in_c, out_c, 3, padding=1), nn.BatchNorm2d(out_c), nn.ReLU(), nn.Conv2d(out_c, out_c, 3, padding=1), nn.BatchNorm2d(out_c), nn.ReLU(), nn.MaxPool2d(2) ) def forward(self, x): x = self.block1(x) x = self.block2(x) x = self.block3(x) x = self.block4(x) x = self.gap(x) x = x.view(x.size(0), -1) x = self.dropout(torch.relu(self.fc1(x))) x = self.fc2(x) return x # TRANSFORM pytorch_transform = transforms.Compose([ transforms.Resize((150, 150)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) _pytorch_model = None _tensorflow_model = None def get_pytorch_model(): global _pytorch_model if _pytorch_model is None: model = CNN(num_classes=len(CLASSES)) model.load_state_dict(torch.load(str(PYTORCH_PATH), map_location="cpu")) model.eval() _pytorch_model = model return _pytorch_model def get_tensorflow_model(): global _tensorflow_model if _tensorflow_model is None: _tensorflow_model = tf.keras.models.load_model(str(TENSORFLOW_PATH), compile=False) return _tensorflow_model class ImageClassifierAPIView(APIView): def post(self, request): serializer = ImageClassifierSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=400) try: if "image" in serializer.validated_data: image = Image.open(serializer.validated_data["image"]).convert("RGB") else: image_url = serializer.validated_data["image_url"] if not image_url.startswith("http"): return Response({"error": "Invalid URL"}, status=400) resp = requests.get(image_url, timeout=10) resp.raise_for_status() image = Image.open(BytesIO(resp.content)).convert("RGB") model_name = serializer.validated_data.get("model", "pytorch") if model_name == "pytorch": result = self._predict_pytorch(image) else: result = self._predict_tensorflow(image) result["model_used"] = model_name return Response(result, status=200) except Exception as e: print("ERROR:", e) return Response({"error": str(e)}, status=400) def _predict_pytorch(self, image): model = get_pytorch_model() tensor = pytorch_transform(image).unsqueeze(0) with torch.no_grad(): outputs = model(tensor) probs = torch.nn.functional.softmax(outputs, dim=1) values, indices = torch.topk(probs, k=len(CLASSES), dim=1) values = values.squeeze().cpu().numpy() indices = indices.squeeze().cpu().numpy() confidence = float(values[0]) all_probs = [ {"class": CLASSES[indices[i]], "probability": float(values[i])} for i in range(len(CLASSES)) ] if confidence < CONFIDENCE_THRESHOLD: return { "predicted_class": "unknown", "confidence": confidence, "all_probabilities": all_probs } return { "predicted_class": CLASSES[indices[0]], "confidence": confidence, "all_probabilities": all_probs } # TENSORFLOW def _predict_tensorflow(self, image): model = get_tensorflow_model() img = image.resize((130, 130)) arr = np.array(img, dtype=np.float32) arr = arr[:, :, ::-1] arr = np.expand_dims(arr, 0) preds = model.predict(arr, verbose=0)[0] sorted_idx = np.argsort(preds)[::-1] confidence = float(preds[sorted_idx[0]]) all_probs = [ {"class": CLASSES[i], "probability": float(preds[i])} for i in sorted_idx ] if confidence < CONFIDENCE_THRESHOLD: return { "predicted_class": "unknown", "confidence": confidence, "all_probabilities": all_probs } return { "predicted_class": CLASSES[sorted_idx[0]], "confidence": confidence, "all_probabilities": all_probs }