Amrender's picture
Update app.py
c06c232 verified
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import Response
import torch
import torch.nn.functional as F
import numpy as np
import cv2
from PIL import Image
import io
import torchvision.transforms.functional as TF
from transformers import SegformerForSemanticSegmentation
app = FastAPI(title="Unified Cartographer API", version="1.0")
# ==========================================
# 1. LOAD MODEL ON SERVER STARTUP
# ==========================================
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Server booting on: {DEVICE}")
class UnifiedCartographer(torch.nn.Module):
def __init__(self, num_classes=5):
super().__init__()
self.model = SegformerForSemanticSegmentation.from_pretrained(
"nvidia/segformer-b4-finetuned-cityscapes-1024-1024",
num_labels=num_classes, ignore_mismatched_sizes=True
)
def forward(self, x):
outputs = self.model(pixel_values=x)
return F.interpolate(outputs.logits, size=x.shape[-2:], mode="bilinear", align_corners=False)
model = UnifiedCartographer(num_classes=5).to(DEVICE)
# ⚠️ Ensure this filename matches your uploaded weights perfectly
model.load_state_dict(torch.load("unified_cartographer_andhra_v4.pth", map_location=DEVICE))
model.eval()
# ==========================================
# 2. THE RENDERING ENGINE (Now with Legend!)
# ==========================================
def draw_legend(canvas):
"""Draws a semi-transparent legend in the top-left corner"""
legend_items = [
("Bldg (High Conf)", (255, 0, 0)), # Red
("Bldg (Med Conf)", (255, 165, 0)), # Orange
("Bldg (Low Conf)", (255, 0, 255)), # Magenta
("Roads", (255, 255, 0)), # Yellow
("Water", (0, 100, 255)), # Blue
("Open Area", (50, 205, 50)) # Green
]
# 1. Create a semi-transparent black background box for readability
overlay = canvas.copy()
cv2.rectangle(overlay, (10, 10), (210, 160), (0, 0, 0), -1)
cv2.addWeighted(overlay, 0.7, canvas, 0.3, 0, canvas)
# 2. Add the title of the legend
cv2.putText(canvas, "AI TACTICAL MAP", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
# 3. Draw the color boxes and labels
y_offset = 55
for label, color in legend_items:
# Draw the little color square
cv2.rectangle(canvas, (20, y_offset - 10), (35, y_offset + 5), color, -1)
# Draw the text next to it
cv2.putText(canvas, label, (45, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 255, 255), 1)
y_offset += 20
return canvas
def draw_hollow_confidence_map(base_img, mask_pred, bldg_probs):
# Darken the original satellite image for the background
canvas = np.ascontiguousarray((base_img * 0.6).astype(np.uint8))
is_bldg = (mask_pred == 1)
# Calculate confidence tiers
mask_high = np.uint8(is_bldg & (bldg_probs >= 0.75))
mask_med = np.uint8(is_bldg & (bldg_probs >= 0.25) & (bldg_probs < 0.75))
mask_low = np.uint8(is_bldg & (bldg_probs < 0.25))
# Draw Hollow Buildings First
cv2.drawContours(canvas, cv2.findContours(mask_high, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0], -1, (255, 0, 0), 3)
cv2.drawContours(canvas, cv2.findContours(mask_med, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0], -1, (255, 165, 0), 3)
cv2.drawContours(canvas, cv2.findContours(mask_low, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0], -1, (255, 0, 255), 3)
# Paint Solid Terrain Underneath
road_mask = np.uint8(mask_pred == 2)
canvas[road_mask == 1] = [255, 255, 0] # Yellow Roads
canvas[mask_pred == 3] = [0, 100, 255] # Blue Water
canvas[mask_pred == 4] = [50, 205, 50] # Green Open Area
# ✨ Add the Legend to the final canvas before returning
canvas = draw_legend(canvas)
return canvas
# ==========================================
# 3. THE API ENDPOINTS
# ==========================================
@app.get("/")
def health_check():
return {"status": "online", "model": "SegFormer-B4", "device": DEVICE}
@app.post("/predict")
async def predict_segmentation(file: UploadFile = File(...)):
# 1. Read the uploaded image into memory
image_bytes = await file.read()
raw_pil = Image.open(io.BytesIO(image_bytes)).convert("RGB").resize((512, 512), Image.BILINEAR)
img_np = np.array(raw_pil)
# 2. Prepare for the AI
tensor_img = TF.to_tensor(raw_pil)
tensor_img = TF.normalize(tensor_img, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
tensor_img = tensor_img.unsqueeze(0).to(DEVICE)
# 3. Run Inference & Extract Probabilities
with torch.no_grad():
logits = model(tensor_img)
mask_pred = torch.argmax(logits, dim=1).squeeze().cpu().numpy()
probs = F.softmax(logits, dim=1).squeeze().cpu().numpy()
bldg_probs = probs[1, :, :] # Extract Class 1 (Buildings) probability map
# 4. Generate the proper tactical map (now with the legend baked in)
output_canvas = draw_hollow_confidence_map(img_np, mask_pred, bldg_probs)
# 5. Compress back to a PNG and send to the user
is_success, buffer = cv2.imencode(".png", cv2.cvtColor(output_canvas, cv2.COLOR_RGB2BGR))
io_buf = io.BytesIO(buffer)
return Response(content=io_buf.getvalue(), media_type="image/png")