import gradio as gr import torch import torch.nn as nn import torchvision.models as models import torchvision.transforms as T from PIL import Image, ImageDraw import json import numpy as np from huggingface_hub import hf_hub_download # ────────────────────────────────────────────────────────────────────────────── # Model definition — must match the architecture used during training # Output: (B, 8) → 4 corners (x,y) normalised to [0, 1], order TL TR BR BL # ────────────────────────────────────────────────────────────────────────────── class ParkingSpaceDetector(nn.Module): def __init__(self): super().__init__() backbone = models.mobilenet_v2(weights=None) self.features = backbone.features self.pool = nn.AdaptiveAvgPool2d(1) self.head = nn.Sequential( nn.Dropout(0.3), nn.Linear(1280, 256), nn.ReLU(inplace=True), nn.Dropout(0.2), nn.Linear(256, 8), nn.Sigmoid(), ) def forward(self, x): return self.head(self.pool(self.features(x)).flatten(1)) # ────────────────────────────────────────────────────────────────────────────── # Model loading # Repo: UmeshAdabala/RectArea_Parkospace (model repo type) # File: best.pt — plain OrderedDict state_dict # (saved with torch.save(model.state_dict(), "best.pt")) # ────────────────────────────────────────────────────────────────────────────── MODEL = None def get_model(): global MODEL if MODEL is not None: return MODEL print("Loading model from HuggingFace Hub ...") try: path = hf_hub_download( repo_id="UmeshAdabala/RectArea_Parkospace", filename="best.pt", repo_type="model", ) ckpt = torch.load(path, map_location="cpu") m = ParkingSpaceDetector() if isinstance(ckpt, dict): # Detect the correct key for the weights if "model_state" in ckpt: state = ckpt["model_state"] # actual format in this repo elif "model_state_dict" in ckpt: state = ckpt["model_state_dict"] elif "state_dict" in ckpt: state = ckpt["state_dict"] else: state = ckpt # bare flat state_dict m.load_state_dict(state, strict=True) m.eval() MODEL = m else: # Full model object was pickled ckpt.eval() MODEL = ckpt print("Model loaded successfully.") except Exception as e: print(f"Model load error: {e} — using random weights (demo mode).") MODEL = ParkingSpaceDetector().eval() return MODEL # ────────────────────────────────────────────────────────────────────────────── # Image preprocessing # ────────────────────────────────────────────────────────────────────────────── TRANSFORM = T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) # ────────────────────────────────────────────────────────────────────────────── # Inference helpers # ────────────────────────────────────────────────────────────────────────────── def run_detection(image: Image.Image): """Run model on image, return pixel-space corners as JSON string.""" if image is None: return None, "Upload a parking space photo first." img = image.convert("RGB") W, H = img.size tensor = TRANSFORM(img).unsqueeze(0) # (1, 3, 224, 224) with torch.no_grad(): raw = get_model()(tensor)[0].tolist() # 8 values in [0, 1] # corners TL TR BR BL — normalised → pixel corners_px = [[raw[i] * W, raw[i + 1] * H] for i in range(0, 8, 2)] payload = json.dumps({"corners": corners_px, "width": W, "height": H}) return payload, "" def compute_area_md(corners_json: str) -> str: """Estimate dimensions + pricing from pixel-space corners JSON.""" if not corners_json: return "" try: data = json.loads(corners_json) corners = data["corners"] # [[x,y], ...] xs = [p[0] for p in corners] ys = [p[1] for p in corners] bw_px = max(xs) - min(xs) bh_px = max(ys) - min(ys) asp = bw_px / (bh_px + 1e-6) L = 5.0 if asp >= 1 else round(min(max(2.5 / asp, 1), 15), 2) B = round(min(max(5.0 / asp, 1), 10), 2) if asp >= 1 else 2.5 area = round(L * B, 2) price_mo = int(area * 10) return f"""## Detection Results | Dimension | Value | |-----------|-------| | Length | **{L} m** | | Breadth | **{B} m** | | Area | **{area} m²** | ### Suggested Pricing | Type | Price | |---------|-------| | Hourly | Rs. 50 | | Daily | Rs. 300 | | Monthly | **Rs. {price_mo}** (area x Rs. 10 / m²) | *Drag the corner handles to fine-tune the detected region.*""" except Exception as e: return f"Error computing area: {e}" # ────────────────────────────────────────────────────────────────────────────── # Gradio callbacks # ────────────────────────────────────────────────────────────────────────────── def on_detect(image): """Button click: run model, return image + corners JSON + info markdown.""" if image is None: return None, "", "Upload an image to begin." pixel_json, err = run_detection(image) if err: return image, "", err return image, pixel_json, compute_area_md(pixel_json) def on_corners_change(corners_json): """Corners were updated by JS drag — recompute and return area markdown.""" return compute_area_md(corners_json) # ────────────────────────────────────────────────────────────────────────────── # Warm up model at startup # ────────────────────────────────────────────────────────────────────────────── get_model() # ────────────────────────────────────────────────────────────────────────────── # CSS # ────────────────────────────────────────────────────────────────────────────── CSS = """ body { background: #0d0d1a !important; } .gradio-container { background: #0d0d1a !important; max-width: 960px !important; margin: 0 auto; } .gr-button-primary { background: #2ED8DF !important; color: #0d0d1a !important; font-weight: 800 !important; border: none !important; border-radius: 8px !important; letter-spacing: 0.04em !important; } .gr-button-primary:hover { background: #12EF86 !important; } footer { display: none !important; } """ # ────────────────────────────────────────────────────────────────────────────── # Canvas HTML + JS # # Flow: # Python → hidden_img updated → JS observer fires → canvas reloads image # Python → corners_state updated → JS observer fires → canvas draws handles # User drags handle → JS pushes JSON → corners_state.change() # → Python recomputes area # ────────────────────────────────────────────────────────────────────────────── CANVAS_BLOCK = """
Detected corners will appear here after clicking Detect.
Upload a parking space photo — AI detects corners — drag to refine — area updates instantly
Tips for best results
Stand at one corner and shoot diagonally
Include all 4 corners in the frame
Good lighting — avoid strong shadows
Works with car parks, open spaces, garages
Model: UmeshAdabala/RectArea_Parkospace | MobileNetV2 + Keypoint Regression | ~13 MB | MIT License