UmeshAdabala commited on
Commit
c3cdd1d
Β·
verified Β·
1 Parent(s): db981f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -447
app.py CHANGED
@@ -8,12 +8,6 @@ import json
8
  import numpy as np
9
  from huggingface_hub import hf_hub_download
10
 
11
-
12
- # ──────────────────────────────────────────────────────────────────────────────
13
- # Model definition β€” must match the architecture used during training
14
- # Output: (B, 8) β†’ 4 corners (x,y) normalised to [0, 1], order TL TR BR BL
15
- # ──────────────────────────────────────────────────────────────────────────────
16
-
17
  class ParkingSpaceDetector(nn.Module):
18
  def __init__(self):
19
  super().__init__()
@@ -21,488 +15,141 @@ class ParkingSpaceDetector(nn.Module):
21
  self.features = backbone.features
22
  self.pool = nn.AdaptiveAvgPool2d(1)
23
  self.head = nn.Sequential(
24
- nn.Dropout(0.3),
25
- nn.Linear(1280, 256),
26
- nn.ReLU(inplace=True),
27
- nn.Dropout(0.2),
28
- nn.Linear(256, 8),
29
- nn.Sigmoid(),
30
  )
31
-
32
  def forward(self, x):
33
  return self.head(self.pool(self.features(x)).flatten(1))
34
 
35
-
36
- # ──────────────────────────────────────────────────────────────────────────────
37
- # Model loading
38
- # Repo: UmeshAdabala/RectArea_Parkospace (model repo type)
39
- # File: best.pt β€” plain OrderedDict state_dict
40
- # (saved with torch.save(model.state_dict(), "best.pt"))
41
- # ──────────────────────────────────────────────────────────────────────────────
42
-
43
  MODEL = None
44
-
45
-
46
  def get_model():
47
  global MODEL
48
- if MODEL is not None:
49
- return MODEL
50
-
51
- print("Loading model from HuggingFace Hub ...")
52
  try:
53
- path = hf_hub_download(
54
- repo_id="UmeshAdabala/RectArea_Parkospace",
55
- filename="best.pt",
56
- repo_type="model",
57
- )
58
- ckpt = torch.load(path, map_location="cpu")
59
-
60
  m = ParkingSpaceDetector()
61
-
62
  if isinstance(ckpt, dict):
63
- # Detect the correct key for the weights
64
- if "model_state" in ckpt:
65
- state = ckpt["model_state"] # actual format in this repo
66
- elif "model_state_dict" in ckpt:
67
- state = ckpt["model_state_dict"]
68
- elif "state_dict" in ckpt:
69
- state = ckpt["state_dict"]
70
- else:
71
- state = ckpt # bare flat state_dict
72
- m.load_state_dict(state, strict=True)
73
- m.eval()
74
- MODEL = m
75
  else:
76
- # Full model object was pickled
77
- ckpt.eval()
78
- MODEL = ckpt
79
-
80
- print("Model loaded successfully.")
81
-
82
  except Exception as e:
83
- print(f"Model load error: {e} β€” using random weights (demo mode).")
84
  MODEL = ParkingSpaceDetector().eval()
85
-
86
  return MODEL
87
 
88
-
89
- # ──────────────────────────────────────────────────────────────────────────────
90
- # Image preprocessing
91
- # ──────────────────────────────────────────────────────────────────────────────
92
-
93
  TRANSFORM = T.Compose([
94
- T.Resize((224, 224)),
95
- T.ToTensor(),
96
- T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
97
  ])
98
 
99
-
100
- # ──────────────────────────────────────────────────────────────────────────────
101
- # Inference helpers
102
- # ───────────────────────────────────────────────────────────��──────────────────
103
-
104
- def run_detection(image: Image.Image):
105
- """Run model on image, return pixel-space corners as JSON string."""
106
  if image is None:
107
- return None, "Upload a parking space photo first."
108
-
109
- img = image.convert("RGB")
110
- W, H = img.size
111
- tensor = TRANSFORM(img).unsqueeze(0) # (1, 3, 224, 224)
112
-
113
- with torch.no_grad():
114
- raw = get_model()(tensor)[0].tolist() # 8 values in [0, 1]
115
-
116
- # corners TL TR BR BL β€” normalised β†’ pixel
117
- corners_px = [[raw[i] * W, raw[i + 1] * H] for i in range(0, 8, 2)]
118
- payload = json.dumps({"corners": corners_px, "width": W, "height": H})
119
- return payload, ""
120
-
121
-
122
- def compute_area_md(corners_json: str) -> str:
123
- """Estimate dimensions + pricing from pixel-space corners JSON."""
124
- if not corners_json:
125
- return ""
126
  try:
127
- data = json.loads(corners_json)
128
- corners = data["corners"] # [[x,y], ...]
129
- xs = [p[0] for p in corners]
130
- ys = [p[1] for p in corners]
131
- bw_px = max(xs) - min(xs)
132
- bh_px = max(ys) - min(ys)
133
- asp = bw_px / (bh_px + 1e-6)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
- L = 5.0 if asp >= 1 else round(min(max(2.5 / asp, 1), 15), 2)
136
- B = round(min(max(5.0 / asp, 1), 10), 2) if asp >= 1 else 2.5
137
- area = round(L * B, 2)
138
  price_mo = int(area * 10)
139
-
140
- return f"""## Detection Results
141
-
142
  | Dimension | Value |
143
  |-----------|-------|
144
- | Length | **{L} m** |
145
- | Breadth | **{B} m** |
146
- | Area | **{area} mΒ²** |
147
-
148
- ### Suggested Pricing
149
-
150
- | Type | Price |
151
- |---------|-------|
152
- | Hourly | Rs. 50 |
153
- | Daily | Rs. 300 |
154
- | Monthly | **Rs. {price_mo}** (area x Rs. 10 / mΒ²) |
155
-
156
- *Drag the corner handles to fine-tune the detected region.*"""
157
  except Exception as e:
158
- return f"Error computing area: {e}"
159
-
160
-
161
- # ──────────────────────────────────────────────────────────────────────────────
162
- # Gradio callbacks
163
- # ──────────────────────────────────────────────────────────────────────────────
164
-
165
- def on_detect(image):
166
- """Button click: run model, return image + corners JSON + info markdown."""
167
- if image is None:
168
- return None, "", "Upload an image to begin."
169
-
170
- pixel_json, err = run_detection(image)
171
- if err:
172
- return image, "", err
173
-
174
- return image, pixel_json, compute_area_md(pixel_json)
175
-
176
-
177
- def on_corners_change(corners_json):
178
- """Corners were updated by JS drag β€” recompute and return area markdown."""
179
- return compute_area_md(corners_json)
180
-
181
-
182
- # ──────────────────────────────────────────────────────────────────────────────
183
- # Warm up model at startup
184
- # ────────────────────────────────────────────────────────────���─────────────────
185
 
186
  get_model()
187
 
188
-
189
- # ──────────────────────────────────────────────────────────────────────────────
190
- # CSS
191
- # ──────────────────────────────────────────────────────────────────────────────
192
-
193
- CSS = """
194
- body { background: #0d0d1a !important; }
195
- .gradio-container {
196
- background: #0d0d1a !important;
197
- max-width: 960px !important;
198
- margin: 0 auto;
199
- }
200
- .gr-button-primary {
201
- background: #2ED8DF !important;
202
- color: #0d0d1a !important;
203
- font-weight: 800 !important;
204
- border: none !important;
205
- border-radius: 8px !important;
206
- letter-spacing: 0.04em !important;
207
- }
208
- .gr-button-primary:hover { background: #12EF86 !important; }
209
- footer { display: none !important; }
210
- """
211
-
212
- # ──────────────────────────────────────────────────────────────────────────────
213
- # Canvas HTML + JS
214
- #
215
- # Flow:
216
- # Python β†’ hidden_img updated β†’ JS observer fires β†’ canvas reloads image
217
- # Python β†’ corners_state updated β†’ JS observer fires β†’ canvas draws handles
218
- # User drags handle β†’ JS pushes JSON β†’ corners_state.change()
219
- # β†’ Python recomputes area
220
- # ──────────────────────────────────────────────────────────────────────────────
221
-
222
- CANVAS_BLOCK = """
223
- <link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
224
-
225
- <div id="canvas-wrap"
226
- style="position:relative;width:100%;touch-action:none;user-select:none;">
227
- <canvas id="parking-canvas"
228
- style="width:100%;height:auto;display:block;border-radius:10px;
229
- border:1px solid #1e1e3a;cursor:crosshair;background:#111127;">
230
- </canvas>
231
- <p id="canvas-hint"
232
- style="color:#4b5563;font-size:11px;font-family:'DM Mono',monospace;
233
- text-align:center;margin:6px 0 0;">
234
- Detected corners will appear here after clicking Detect.
235
- </p>
236
- </div>
237
-
238
- <script>
239
- (function () {
240
- 'use strict';
241
-
242
- const canvas = document.getElementById('parking-canvas');
243
- const ctx = canvas.getContext('2d');
244
- const hint = document.getElementById('canvas-hint');
245
-
246
- let imgObj = null;
247
- let corners = []; // pixel coords in native image resolution
248
- let origW = 1;
249
- let origH = 1;
250
- let dragging = -1;
251
-
252
- // Hit radius in CSS pixels β€” generous for touch
253
- const HIT_CSS = 24;
254
-
255
- // ── Render ─────────────────────────────────────────────────────────────────
256
-
257
- function draw() {
258
- ctx.clearRect(0, 0, canvas.width, canvas.height);
259
- if (imgObj) ctx.drawImage(imgObj, 0, 0);
260
- if (corners.length < 4) return;
261
-
262
- // Polygon fill + stroke
263
- ctx.beginPath();
264
- corners.forEach(([x, y], i) => (i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)));
265
- ctx.closePath();
266
- ctx.fillStyle = 'rgba(46,216,223,0.18)';
267
- ctx.strokeStyle = 'rgba(46,216,223,0.85)';
268
- ctx.lineWidth = Math.max(2, origW / 220);
269
- ctx.fill();
270
- ctx.stroke();
271
-
272
- // Handles β€” scaled so they look the same regardless of image resolution
273
- const scale = origW / canvas.getBoundingClientRect().width;
274
- const r = HIT_CSS * scale * 0.72;
275
- corners.forEach(([x, y]) => {
276
- ctx.beginPath();
277
- ctx.arc(x, y, r, 0, Math.PI * 2);
278
- ctx.fillStyle = 'rgba(46,216,223,0.9)';
279
- ctx.fill();
280
- ctx.beginPath();
281
- ctx.arc(x, y, r * 0.42, 0, Math.PI * 2);
282
- ctx.fillStyle = '#ffffff';
283
- ctx.fill();
284
- });
285
- }
286
-
287
- // ── Pointer helpers ────────────────────────────────────────────────────────
288
-
289
- function canvasXY(e) {
290
- const rect = canvas.getBoundingClientRect();
291
- const scaleX = canvas.width / rect.width;
292
- const scaleY = canvas.height / rect.height;
293
- const src = e.touches ? e.touches[0] : e;
294
- return [
295
- (src.clientX - rect.left) * scaleX,
296
- (src.clientY - rect.top) * scaleY,
297
- ];
298
- }
299
-
300
- function findHandle(pos) {
301
- const rect = canvas.getBoundingClientRect();
302
- const hitPx = HIT_CSS * (canvas.width / rect.width) * 1.3;
303
- let best = -1, bestD = Infinity;
304
- corners.forEach(([x, y], i) => {
305
- const d = Math.hypot(pos[0] - x, pos[1] - y);
306
- if (d < hitPx && d < bestD) { bestD = d; best = i; }
307
- });
308
- return best;
309
- }
310
-
311
- // ── Event listeners ────────────────────────────────────────────────────────
312
-
313
- canvas.addEventListener('mousedown', down, { passive: false });
314
- canvas.addEventListener('mousemove', move, { passive: false });
315
- canvas.addEventListener('mouseup', up);
316
- canvas.addEventListener('mouseleave', up);
317
- canvas.addEventListener('touchstart', down, { passive: false });
318
- canvas.addEventListener('touchmove', move, { passive: false });
319
- canvas.addEventListener('touchend', up);
320
-
321
- function down(e) {
322
- e.preventDefault();
323
- dragging = findHandle(canvasXY(e));
324
- }
325
-
326
- function move(e) {
327
- if (dragging < 0) return;
328
- e.preventDefault();
329
- const [cx, cy] = canvasXY(e);
330
- corners[dragging] = [
331
- Math.max(0, Math.min(origW, cx)),
332
- Math.max(0, Math.min(origH, cy)),
333
- ];
334
- draw();
335
- }
336
-
337
- function up() {
338
- if (dragging >= 0) pushCorners();
339
- dragging = -1;
340
- }
341
-
342
- // ── Push corners β†’ Gradio hidden Textbox ──────────────────────────────────
343
-
344
- function pushCorners() {
345
- const ta = document.querySelector('#corners-state-box textarea');
346
- if (!ta) return;
347
- const payload = JSON.stringify({
348
- corners: corners.map(([x, y]) => [x, y]),
349
- width: origW,
350
- height: origH,
351
- });
352
- const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
353
- setter.call(ta, payload);
354
- ta.dispatchEvent(new Event('input', { bubbles: true }));
355
- }
356
-
357
- // ── Bridge: observe DOM for Gradio updates ─────────────────────────────────
358
-
359
- let lastImgSrc = '';
360
- let lastCornersVal = '';
361
-
362
- const obs = new MutationObserver(() => {
363
-
364
- // 1. Hidden image updated β†’ reload canvas image
365
- const imgEl = document.querySelector('#hidden-img-out img');
366
- if (imgEl && imgEl.src && imgEl.src !== lastImgSrc) {
367
- lastImgSrc = imgEl.src;
368
- const img = new Image();
369
- img.crossOrigin = 'anonymous';
370
- img.onload = () => {
371
- imgObj = img;
372
- origW = img.naturalWidth;
373
- origH = img.naturalHeight;
374
- canvas.width = origW;
375
- canvas.height = origH;
376
- corners = [];
377
- draw();
378
- hint.textContent = 'Image loaded. Click Detect to find corners.';
379
- };
380
- img.src = lastImgSrc;
381
- }
382
-
383
- // 2. Corners state textbox updated by Python β†’ parse and render
384
- const ta = document.querySelector('#corners-state-box textarea');
385
- if (ta && ta.value && ta.value !== lastCornersVal) {
386
- lastCornersVal = ta.value;
387
- try {
388
- const data = JSON.parse(ta.value);
389
- if (data && Array.isArray(data.corners) && data.corners.length === 4) {
390
- origW = data.width || origW;
391
- origH = data.height || origH;
392
- corners = data.corners.map(([x, y]) => [x, y]);
393
- draw();
394
- hint.textContent = 'Drag the handles to fine-tune the detected area.';
395
- }
396
- } catch (_) {}
397
- }
398
- });
399
-
400
- obs.observe(document.body, { childList: true, subtree: true, attributes: true });
401
-
402
- })();
403
- </script>
404
- """
405
-
406
- # ──────────────────────────────────────────────────────────────────────────────
407
- # Gradio layout
408
- # ──────────────────────────────────────────────────────────────────────────────
409
-
410
- with gr.Blocks(title="ParkoSpace β€” AI Area Detector") as demo:
411
-
412
- # Inline CSS β€” works across all Gradio versions
413
- gr.HTML(f"<style>{CSS}</style>")
414
-
415
  gr.HTML("""
416
- <link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
417
- <div style="text-align:center;padding:32px 16px 16px">
418
- <div style="font-size:11px;letter-spacing:0.25em;color:#2ED8DF;
419
- font-family:'DM Mono',monospace;text-transform:uppercase;margin-bottom:6px">
420
- AI Parking Area Detector
421
- </div>
422
- <h1 style="font-family:'DM Mono',monospace;font-size:26px;font-weight:500;
423
- color:#f0f4f8;margin:0;letter-spacing:-0.02em">ParkoSpace</h1>
424
- <p style="color:#4b5563;font-size:12px;margin-top:8px;
425
- font-family:'DM Mono',monospace;letter-spacing:0.03em">
426
- Upload a parking space photo β€” AI detects corners β€” drag to refine β€” area updates instantly
427
- </p>
428
  </div>
429
  """)
430
 
431
- with gr.Row(equal_height=False):
432
-
433
- # Left column: upload + detect button
434
  with gr.Column(scale=1):
435
- inp = gr.Image(
436
- type="pil",
437
- label="Upload Parking Space Photo",
438
- height=360,
439
- )
440
- btn = gr.Button("Detect Parking Area", variant="primary", size="lg")
441
- gr.HTML("""
442
- <div style="background:#111127;border:1px solid #1e1e3a;border-radius:10px;
443
- padding:12px 14px;margin-top:8px">
444
- <p style="color:#4b5563;font-size:11px;font-family:'DM Mono',monospace;
445
- margin:0;line-height:1.9">
446
- <span style="color:#2ED8DF">Tips for best results</span><br>
447
- Stand at one corner and shoot diagonally<br>
448
- Include all 4 corners in the frame<br>
449
- Good lighting β€” avoid strong shadows<br>
450
- Works with car parks, open spaces, garages
451
  </p>
452
- </div>
453
- """)
454
-
455
- # Right column: canvas + results
456
  with gr.Column(scale=1):
457
- gr.HTML(CANVAS_BLOCK)
458
- out_md = gr.Markdown("*Upload a photo and click Detect to see results.*")
459
-
460
- # ── Hidden bridge components ───────────────────────────────────────────────
461
 
462
- # Holds pixel-space corners JSON; written by Python after detection
463
- # and by JS after a drag.
464
- corners_state = gr.Textbox(
465
- value="",
466
- visible=False,
467
- label="corners_state",
468
- elem_id="corners-state-box",
469
- )
470
-
471
- # Holds the PIL image; Python writes here so JS can grab the img src URL.
472
- hidden_img = gr.Image(
473
- type="pil",
474
- visible=False,
475
- label="hidden_img",
476
- elem_id="hidden-img-out",
477
- )
478
-
479
- # ── Event wiring ───────────────────────────────────────────────────────────
480
-
481
- btn.click(
482
- fn=on_detect,
483
- inputs=[inp],
484
- outputs=[hidden_img, corners_state, out_md],
485
- )
486
-
487
- corners_state.change(
488
- fn=on_corners_change,
489
- inputs=[corners_state],
490
- outputs=[out_md],
491
- )
492
 
493
  gr.HTML("""
494
- <div style="margin-top:24px;padding:14px;background:#111127;border-radius:10px;
495
- border:1px solid #1e1e3a">
496
- <p style="color:#2e3650;font-size:11px;font-family:'DM Mono',monospace;
497
- text-align:center;margin:0">
498
- Model: UmeshAdabala/RectArea_Parkospace &nbsp;|&nbsp;
499
- MobileNetV2 + Keypoint Regression &nbsp;|&nbsp; ~13 MB &nbsp;|&nbsp; MIT License
500
  </p>
501
  </div>
502
  """)
503
 
504
-
505
  if __name__ == "__main__":
506
- demo.launch()
507
- else:
508
  demo.launch()
 
8
  import numpy as np
9
  from huggingface_hub import hf_hub_download
10
 
 
 
 
 
 
 
11
  class ParkingSpaceDetector(nn.Module):
12
  def __init__(self):
13
  super().__init__()
 
15
  self.features = backbone.features
16
  self.pool = nn.AdaptiveAvgPool2d(1)
17
  self.head = nn.Sequential(
18
+ nn.Dropout(0.3), nn.Linear(1280, 256),
19
+ nn.ReLU(inplace=True), nn.Dropout(0.2),
20
+ nn.Linear(256, 8), nn.Sigmoid()
 
 
 
21
  )
 
22
  def forward(self, x):
23
  return self.head(self.pool(self.features(x)).flatten(1))
24
 
 
 
 
 
 
 
 
 
25
  MODEL = None
 
 
26
  def get_model():
27
  global MODEL
28
+ if MODEL: return MODEL
 
 
 
29
  try:
30
+ path = hf_hub_download("UmeshAdabala/RectArea_Parkospace", "best.pt", repo_type="model")
 
 
 
 
 
 
31
  m = ParkingSpaceDetector()
32
+ ckpt = torch.load(path, map_location="cpu")
33
  if isinstance(ckpt, dict):
34
+ key = next((k for k in ("model_state_dict","state_dict") if k in ckpt), None)
35
+ m.load_state_dict(ckpt[key] if key else ckpt)
 
 
 
 
 
 
 
 
 
 
36
  else:
37
+ MODEL = ckpt; MODEL.eval(); return MODEL
38
+ m.eval(); MODEL = m
 
 
 
 
39
  except Exception as e:
40
+ print(f"Model load error: {e}")
41
  MODEL = ParkingSpaceDetector().eval()
 
42
  return MODEL
43
 
 
 
 
 
 
44
  TRANSFORM = T.Compose([
45
+ T.Resize((224, 224)), T.ToTensor(),
46
+ T.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),
 
47
  ])
48
 
49
+ def detect_and_draw(image: Image.Image):
 
 
 
 
 
 
50
  if image is None:
51
+ return None, "❌ Upload a parking space photo first."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  try:
53
+ img = image.convert("RGB")
54
+ W, H = img.size
55
+ t = TRANSFORM(img).unsqueeze(0)
56
+ with torch.no_grad():
57
+ c = get_model()(t)[0].tolist()
58
+
59
+ # corners: TL TR BR BL (normalised)
60
+ corners = [(c[i]*W, c[i+1]*H) for i in range(0, 8, 2)]
61
+ xs = [p[0] for p in corners]; ys = [p[1] for p in corners]
62
+ bw = max(xs)-min(xs); bh = max(ys)-min(ys)
63
+ asp = bw / (bh + 1e-6)
64
+ L = 5.0 if asp >= 1 else round(min(max(2.5/asp,1),15),2)
65
+ B = round(min(max(5.0/asp,1),10),2) if asp >= 1 else 2.5
66
+ area = round(L*B, 2)
67
+
68
+ # Draw overlay on image
69
+ out = img.copy().convert("RGBA")
70
+ overlay = Image.new("RGBA", out.size, (0,0,0,0))
71
+ draw = ImageDraw.Draw(overlay)
72
+
73
+ # Shaded region
74
+ poly = [(int(p[0]), int(p[1])) for p in corners]
75
+ draw.polygon(poly, fill=(46,216,223,60), outline=None)
76
+ draw.line(poly + [poly[0]], fill=(46,216,223,255), width=3)
77
+
78
+ # Corner dots
79
+ for px,py in poly:
80
+ draw.ellipse([px-8,py-8,px+8,py+8], fill=(46,216,223,255))
81
+ draw.ellipse([px-4,py-4,px+4,py+4], fill=(255,255,255,255))
82
+
83
+ out = Image.alpha_composite(out, overlay).convert("RGB")
84
 
 
 
 
85
  price_mo = int(area * 10)
86
+ info = f"""## βœ… Detection Complete
 
 
87
  | Dimension | Value |
88
  |-----------|-------|
89
+ | πŸ“ Length | **{L} m** |
90
+ | πŸ“ Breadth | **{B} m** |
91
+ | πŸ“¦ Area | **{area} mΒ²** |
92
+ ### πŸ’° Suggested Pricing
93
+ | Type | Price |
94
+ |------|-------|
95
+ | ⏱ Hourly | **β‚Ή50** |
96
+ | πŸ“… Daily | **β‚Ή300** |
97
+ | πŸ—“ Monthly | **β‚Ή{price_mo}** (area Γ— β‚Ή10/mΒ²) |
98
+ ---
99
+ *Drag the corner handles on the image to fine-tune the detected region.*"""
100
+ return out, info
 
101
  except Exception as e:
102
+ return image, f"❌ Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  get_model()
105
 
106
+ with gr.Blocks(
107
+ title="ParkoSpace β€” AI Area Detector",
108
+ css="""
109
+ body { background: #0d0d1a !important; }
110
+ .gradio-container { background: #0d0d1a !important; max-width: 900px !important; margin: 0 auto; }
111
+ h1 { color: #2ED8DF !important; font-family: 'Space Grotesk', sans-serif; }
112
+ .gr-button-primary { background: #2ED8DF !important; color: #0d0d1a !important; font-weight: 800 !important; border: none !important; }
113
+ .gr-button-primary:hover { background: #12EF86 !important; }
114
+ footer { display: none !important; }
115
+ """
116
+ ) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  gr.HTML("""
118
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700;800&display=swap" rel="stylesheet">
119
+ <div style="text-align:center;padding:32px 16px 8px;background:linear-gradient(180deg,#0d0d1a,#0d0d1a)">
120
+ <div style="font-size:40px;margin-bottom:8px">πŸ…Ώ</div>
121
+ <h1 style="font-family:'Space Grotesk',sans-serif;font-size:28px;font-weight:800;color:#2ED8DF;margin:0">ParkoSpace AI Detector</h1>
122
+ <p style="color:#6b7280;font-size:14px;margin-top:6px;font-family:monospace">Upload a parking space photo β†’ AI detects the area β†’ get dimensions + pricing</p>
 
 
 
 
 
 
 
123
  </div>
124
  """)
125
 
126
+ with gr.Row():
 
 
127
  with gr.Column(scale=1):
128
+ inp = gr.Image(type="pil", label="πŸ“Έ Upload Parking Space Photo", height=340)
129
+ btn = gr.Button("πŸ” Detect Parking Area", variant="primary", size="lg")
130
+ gr.HTML("""<div style="background:#1a1a2e;border:1px solid #2ED8DF22;border-radius:12px;padding:12px;margin-top:8px">
131
+ <p style="color:#6b7280;font-size:11px;font-family:monospace;margin:0;line-height:1.6">
132
+ πŸ“Œ <b style="color:#2ED8DF">Tips for best results:</b><br>
133
+ β€’ Stand at one corner, shoot diagonally<br>
134
+ β€’ Include all 4 corners in the frame<br>
135
+ β€’ Good lighting, avoid shadows<br>
136
+ β€’ Works with car parks, open spaces, garages
 
 
 
 
 
 
 
137
  </p>
138
+ </div>""")
 
 
 
139
  with gr.Column(scale=1):
140
+ out_img = gr.Image(type="pil", label="🎯 Detected Area", height=340)
141
+ out_md = gr.Markdown("*Upload a photo and click Detect to see results.*")
 
 
142
 
143
+ btn.click(fn=detect_and_draw, inputs=inp, outputs=[out_img, out_md])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  gr.HTML("""
146
+ <div style="margin-top:24px;padding:16px;background:#1a1a2e;border-radius:12px;border:1px solid #ffffff11">
147
+ <p style="color:#4b5563;font-size:11px;font-family:monospace;text-align:center;margin:0">
148
+ Model: <span style="color:#2ED8DF">UmeshAdabala/RectArea_Parkospace</span> Β· MobileNetV2 + Keypoint Regression Β· ~13MB<br>
149
+ This Space powers the <b style="color:#12EF86">ParkoSpace India</b> platform
 
 
150
  </p>
151
  </div>
152
  """)
153
 
 
154
  if __name__ == "__main__":
 
 
155
  demo.launch()