mlbench123 commited on
Commit
d39741c
Β·
verified Β·
1 Parent(s): 1ba1f57

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -198
app.py CHANGED
@@ -1,47 +1,25 @@
1
  """
2
- =============================================================
3
- HuggingFace Spaces β€” Gradio Demo
4
- Mudflap / Black-sheet Detection
5
- Models: YOLOv11x + YOLOv5x
6
- =============================================================
7
- File layout in your HF Space repo:
8
- app.py ← this file
9
- requirements.txt
10
- models/
11
- yolov11x_mudflap_best.pt
12
- yolov5x_mudflap_best.pt
13
- examples/
14
- example1.jpg
15
- example2.jpg
16
- =============================================================
17
  """
18
 
19
  import os
20
  import gradio as gr
21
  import torch
22
  import numpy as np
23
- import cv2
24
  from pathlib import Path
25
  from PIL import Image, ImageDraw, ImageFont
26
  import time
27
 
28
- # Allow Ultralytics to write to /tmp (needed on HF Spaces)
29
  os.environ.setdefault("YOLO_CONFIG_DIR", "/tmp/Ultralytics")
30
 
31
- # ─────────────────────────────────────────────────────────────
32
- # 1. MODEL LOADING
33
- # ─────────────────────────────────────────────────────────────
34
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
35
- MODEL_DIR = Path("models")
36
- CLASS_NAMES = ["mudflap"]
37
-
38
- # Colour palette per model
39
- COLORS = {
40
- "YOLOv11x": (0, 200, 100), # green
41
- "YOLOv5x" : (255, 140, 0), # orange
42
- }
43
 
44
- # ── YOLOv11x (Ultralytics) ────────────────────────────────────
45
  def load_v11():
46
  from ultralytics import YOLO
47
  m = YOLO(str(MODEL_DIR / "yolov11x_mudflap_best.pt"))
@@ -49,168 +27,99 @@ def load_v11():
49
  print("[βœ“] YOLOv11x loaded.")
50
  return m
51
 
52
- # ── YOLOv5x (torch.hub) ───────────────────────────────────────
53
  def load_v5():
54
- # trust_repo=True ← fixes the interactive-prompt EOFError on HF Spaces
55
  m = torch.hub.load(
56
  "ultralytics/yolov5", "custom",
57
  path=str(MODEL_DIR / "yolov5x_mudflap_best.pt"),
58
- force_reload=False,
59
- trust_repo=True, # ← KEY FIX
60
- device=DEVICE,
61
  )
62
- m.conf = 0.60
63
- m.iou = 0.45
64
- m.max_det = 100
65
  print("[βœ“] YOLOv5x loaded.")
66
  return m
67
 
68
- # Load once at startup
69
  model_v11 = load_v11()
70
  model_v5 = load_v5()
71
 
72
-
73
- # ─────────────────────────────────────────────────────────────
74
- # 2. INFERENCE HELPERS
75
- # ─────────────────────────────────────────────────────────────
76
  def run_v11(img_np, conf_thr, iou_thr):
77
- """Returns list of (x1,y1,x2,y2,conf,cls_id)."""
78
- results = model_v11.predict(
79
- source=img_np, conf=conf_thr, iou=iou_thr,
80
- imgsz=640, device=DEVICE, verbose=False
81
- )
82
  boxes = []
83
  for r in results:
84
  for b in r.boxes:
85
  x1, y1, x2, y2 = b.xyxy[0].cpu().tolist()
86
- conf = float(b.conf[0])
87
- cls = int(b.cls[0])
88
- boxes.append((x1, y1, x2, y2, conf, cls))
89
  return boxes
90
 
 
 
 
 
 
91
 
92
  def run_v5(img_np, conf_thr, iou_thr):
93
- """Returns list of (x1,y1,x2,y2,conf,cls_id)."""
94
  model_v5.conf = conf_thr
95
  model_v5.iou = iou_thr
96
  results = model_v5(img_np, size=640)
97
- boxes = []
98
  for *xyxy, conf, cls in results.xyxy[0].cpu().tolist():
99
  boxes.append((xyxy[0], xyxy[1], xyxy[2], xyxy[3], conf, int(cls)))
100
  return boxes
101
 
102
-
103
- # ─────────────────────────────────────────────────────────────
104
- # 3. DRAWING UTILITY
105
- # ─────────────────────────────────────────────────────────────
106
- def draw_boxes(img_pil: Image.Image, detections: list,
107
- color: tuple, label_prefix: str) -> Image.Image:
108
  draw = ImageDraw.Draw(img_pil)
109
  try:
110
- font = ImageFont.truetype(
111
- "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
112
  except Exception:
113
  font = ImageFont.load_default()
114
-
115
  for (x1, y1, x2, y2, conf, cls) in detections:
116
  x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
117
- lbl = f"{label_prefix} {CLASS_NAMES[cls]} {conf:.2f}"
118
  r, g, b = color
119
-
120
- # Bounding box (thick border)
121
  for t in range(3):
122
  draw.rectangle([x1-t, y1-t, x2+t, y2+t], outline=(r, g, b))
123
-
124
- # Label background
125
- tw, th = 0, 0
126
  try:
127
- bbox_txt = draw.textbbox((x1, y1 - 22), lbl, font=font)
128
- tw = bbox_txt[2] - bbox_txt[0]
129
- th = bbox_txt[3] - bbox_txt[1]
130
  except Exception:
131
- tw, th = len(lbl) * 8, 16
132
-
133
- draw.rectangle([x1, y1 - th - 4, x1 + tw + 4, y1], fill=(r, g, b))
134
- draw.text((x1 + 2, y1 - th - 2), lbl, fill=(255, 255, 255), font=font)
135
-
136
  return img_pil
137
 
138
-
139
- def side_by_side(img_orig: Image.Image,
140
- dets_v11: list,
141
- dets_v5 : list) -> Image.Image:
142
- """Create a 3-panel image: original | v11 result | v5 result."""
143
  w, h = img_orig.size
144
- canvas = Image.new("RGB", (w * 3 + 20, h + 40), (30, 30, 30))
145
-
146
- draw = ImageDraw.Draw(canvas)
147
  try:
148
- font_h = ImageFont.truetype(
149
- "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
150
  except Exception:
151
- font_h = ImageFont.load_default()
152
-
153
- titles = ["Original", "YOLOv11x", "YOLOv5x"]
154
- for i, title in enumerate(titles):
155
- draw.text((i * (w + 10) + w // 2 - 40, 5), title,
156
- fill=(255, 255, 255), font=font_h)
157
-
158
- panel_orig = img_orig.copy()
159
- panel_v11 = draw_boxes(img_orig.copy(), dets_v11, COLORS["YOLOv11x"], "v11")
160
- panel_v5 = draw_boxes(img_orig.copy(), dets_v5, COLORS["YOLOv5x"], "v5")
161
-
162
- canvas.paste(panel_orig, (0, 40))
163
- canvas.paste(panel_v11, (w + 10, 40))
164
- canvas.paste(panel_v5, (2*w + 20, 40))
165
  return canvas
166
 
167
-
168
- # ─────────────────────────────────────────────────────────────
169
- # 4. MAIN INFERENCE FUNCTION (called by Gradio)
170
- # ─────────────────────────────────────────────────────────────
171
- def detect(image: Image.Image,
172
- model_choice: str,
173
- conf_thr: float,
174
- iou_thr: float):
175
  if image is None:
176
- return None, "⚠️ Please upload an image."
177
-
178
  img_np = np.array(image.convert("RGB"))
179
- t0 = time.time()
180
-
181
- dets_v11, dets_v5 = [], []
182
- stats_lines = []
183
-
184
- if model_choice in ("YOLOv11x", "Both (side-by-side)"):
185
- t1 = time.time()
186
- dets_v11 = run_v11(img_np, conf_thr, iou_thr)
187
- dt_v11 = time.time() - t1
188
- stats_lines.append(
189
- f"**YOLOv11x** β€” {len(dets_v11)} detection(s) in {dt_v11*1000:.1f} ms"
190
- )
191
- for d in dets_v11:
192
- stats_lines.append(
193
- f" β€’ {CLASS_NAMES[d[5]]} conf={d[4]:.3f} "
194
- f"box=[{int(d[0])},{int(d[1])},{int(d[2])},{int(d[3])}]"
195
- )
196
 
197
- if model_choice in ("YOLOv5x", "Both (side-by-side)"):
198
- t1 = time.time()
199
- dets_v5 = run_v5(img_np, conf_thr, iou_thr)
200
- dt_v5 = time.time() - t1
201
- stats_lines.append(
202
- f"**YOLOv5x** β€” {len(dets_v5)} detection(s) in {dt_v5*1000:.1f} ms"
203
- )
204
- for d in dets_v5:
205
- stats_lines.append(
206
- f" β€’ {CLASS_NAMES[d[5]]} conf={d[4]:.3f} "
207
- f"box=[{int(d[0])},{int(d[1])},{int(d[2])},{int(d[3])}]"
208
- )
209
 
210
- total_ms = (time.time() - t0) * 1000
211
- stats_lines.append(
212
- f"\n⏱ Total wall-time: {total_ms:.1f} ms | Device: {DEVICE.upper()}"
213
- )
 
214
 
215
  if model_choice == "YOLOv11x":
216
  out = draw_boxes(image.copy(), dets_v11, COLORS["YOLOv11x"], "v11")
@@ -219,52 +128,120 @@ def detect(image: Image.Image,
219
  else:
220
  out = side_by_side(image, dets_v11, dets_v5)
221
 
222
- return out, "\n".join(stats_lines)
 
 
 
 
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- # ─────────────────────────────────────────────────────────────
226
- # 5. GRADIO UI
227
- # ─────────────────────────────────────────────────────────────
228
- DESCRIPTION = """
229
- ## πŸš› Trailer Mudflap / Black-sheet Detector
230
- Detects the **black mud-flap panels** on the sides of Amazon Prime trailers.
 
 
 
 
 
 
 
 
 
 
231
 
232
- ### Models
233
- | Model | Architecture | Notes |
234
- |-------|-------------|-------|
235
- | **YOLOv11x** | Ultralytics YOLO v11 X | Latest generation |
236
- | **YOLOv5x** | Ultralytics YOLOv5 X | Battle-tested baseline |
237
 
238
- Both models were fine-tuned with **heavy augmentation** (mosaic, mixup, perspective, weather,
239
- blur, random shadows, coarse-dropout …) on 300 augmented images from 25 originals.
240
 
241
- Select *Both* to compare the two models side-by-side.
 
242
  """
243
 
244
- with gr.Blocks(title="Mudflap Detector", theme=gr.themes.Soft()) as demo:
245
- gr.Markdown(DESCRIPTION)
246
 
247
- with gr.Row():
248
- with gr.Column(scale=1):
249
- inp_image = gr.Image(type="pil", label="Input Image")
250
- model_sel = gr.Radio(
251
- choices=["YOLOv11x", "YOLOv5x", "Both (side-by-side)"],
252
- value="Both (side-by-side)",
253
- label="Model",
254
- )
255
- conf_slider = gr.Slider(
256
- 0.10, 0.95, value=0.60, step=0.05,
257
- label="Confidence Threshold"
 
 
 
 
 
 
 
 
258
  )
259
- iou_slider = gr.Slider(
260
- 0.10, 0.90, value=0.45, step=0.05,
261
- label="IoU (NMS) Threshold"
 
 
262
  )
263
- run_btn = gr.Button("πŸ” Detect", variant="primary")
264
 
265
- with gr.Column(scale=2):
266
- out_image = gr.Image(type="pil", label="Detection Result")
267
- out_stats = gr.Markdown(label="Stats")
 
 
 
 
 
 
 
268
 
269
  run_btn.click(
270
  fn=detect,
@@ -272,24 +249,5 @@ with gr.Blocks(title="Mudflap Detector", theme=gr.themes.Soft()) as demo:
272
  outputs=[out_image, out_stats],
273
  )
274
 
275
- gr.Examples(
276
- examples=[
277
- ["examples/example1.jpg", "Both (side-by-side)", 0.60, 0.45],
278
- ["examples/example2.jpg", "YOLOv11x", 0.60, 0.45],
279
- ],
280
- inputs=[inp_image, model_sel, conf_slider, iou_slider],
281
- outputs=[out_image, out_stats],
282
- fn=detect,
283
- cache_examples=True,
284
- label="Example Images",
285
- )
286
-
287
- gr.Markdown("""
288
- ---
289
- **Legend:**
290
- 🟩 Green boxes β†’ YOLOv11x | 🟠 Orange boxes β†’ YOLOv5x
291
- **Class:** `mudflap` (the black panels on trailer sides)
292
- """)
293
-
294
  if __name__ == "__main__":
295
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
1
  """
2
+ HuggingFace Spaces β€” Gradio Demo
3
+ Mudflap / Black-sheet Detection | Models: YOLOv11x + YOLOv5x
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  """
5
 
6
  import os
7
  import gradio as gr
8
  import torch
9
  import numpy as np
 
10
  from pathlib import Path
11
  from PIL import Image, ImageDraw, ImageFont
12
  import time
13
 
 
14
  os.environ.setdefault("YOLO_CONFIG_DIR", "/tmp/Ultralytics")
15
 
16
+ # ── Config ────────────────────────────────────────────────────
17
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
18
+ MODEL_DIR = Path("models")
19
+ CLASS_NAMES = ["mudflap"]
20
+ COLORS = {"YOLOv11x": (0, 200, 100), "YOLOv5x": (255, 140, 0)}
 
 
 
 
 
 
 
21
 
22
+ # ── Model loading ─────────────────────────────────────────────
23
  def load_v11():
24
  from ultralytics import YOLO
25
  m = YOLO(str(MODEL_DIR / "yolov11x_mudflap_best.pt"))
 
27
  print("[βœ“] YOLOv11x loaded.")
28
  return m
29
 
 
30
  def load_v5():
 
31
  m = torch.hub.load(
32
  "ultralytics/yolov5", "custom",
33
  path=str(MODEL_DIR / "yolov5x_mudflap_best.pt"),
34
+ force_reload=False, trust_repo=True, device=DEVICE,
 
 
35
  )
36
+ m.conf = 0.60; m.iou = 0.45; m.max_det = 100
 
 
37
  print("[βœ“] YOLOv5x loaded.")
38
  return m
39
 
 
40
  model_v11 = load_v11()
41
  model_v5 = load_v5()
42
 
43
+ # ── Inference ─────────────────────────────────────────────────
 
 
 
44
  def run_v11(img_np, conf_thr, iou_thr):
45
+ results = model_v11.predict(source=img_np, conf=conf_thr, iou=iou_thr,
46
+ imgsz=640, device=DEVICE, verbose=False)
 
 
 
47
  boxes = []
48
  for r in results:
49
  for b in r.boxes:
50
  x1, y1, x2, y2 = b.xyxy[0].cpu().tolist()
51
+ boxes.append((x1, y1, x2, y2, float(b.conf[0]), int(b.cls[0])))
 
 
52
  return boxes
53
 
54
+ def run_v5(img_np, conf_thr, iou_thr):
55
+ model_v5.conf = conf_thr; model_v5.iou = iou_thr
56
+ results = model_v5(img_np, size=640)
57
+ return [(x[0], x[1], x[2], x[3], x[4], int(x[5]))
58
+ for *x, _ in [r for r in results.xyxy[0].cpu().tolist()]]
59
 
60
  def run_v5(img_np, conf_thr, iou_thr):
 
61
  model_v5.conf = conf_thr
62
  model_v5.iou = iou_thr
63
  results = model_v5(img_np, size=640)
64
+ boxes = []
65
  for *xyxy, conf, cls in results.xyxy[0].cpu().tolist():
66
  boxes.append((xyxy[0], xyxy[1], xyxy[2], xyxy[3], conf, int(cls)))
67
  return boxes
68
 
69
+ # ── Drawing ───────────────────────────────────────────────────
70
+ def draw_boxes(img_pil, detections, color, prefix):
 
 
 
 
71
  draw = ImageDraw.Draw(img_pil)
72
  try:
73
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
 
74
  except Exception:
75
  font = ImageFont.load_default()
 
76
  for (x1, y1, x2, y2, conf, cls) in detections:
77
  x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
78
+ lbl = f"{prefix} {CLASS_NAMES[cls]} {conf:.2f}"
79
  r, g, b = color
 
 
80
  for t in range(3):
81
  draw.rectangle([x1-t, y1-t, x2+t, y2+t], outline=(r, g, b))
 
 
 
82
  try:
83
+ bb = draw.textbbox((x1, y1-22), lbl, font=font)
84
+ tw, th = bb[2]-bb[0], bb[3]-bb[1]
 
85
  except Exception:
86
+ tw, th = len(lbl)*8, 16
87
+ draw.rectangle([x1, y1-th-4, x1+tw+4, y1], fill=(r, g, b))
88
+ draw.text((x1+2, y1-th-2), lbl, fill=(255, 255, 255), font=font)
 
 
89
  return img_pil
90
 
91
+ def side_by_side(img_orig, dets_v11, dets_v5):
 
 
 
 
92
  w, h = img_orig.size
93
+ canvas = Image.new("RGB", (w*3+20, h+40), (18, 18, 18))
94
+ draw = ImageDraw.Draw(canvas)
 
95
  try:
96
+ fh = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
 
97
  except Exception:
98
+ fh = ImageFont.load_default()
99
+ for i, title in enumerate(["Original", "YOLOv11x", "YOLOv5x"]):
100
+ draw.text((i*(w+10)+w//2-40, 8), title, fill=(200, 200, 200), font=fh)
101
+ canvas.paste(img_orig.copy(), (0, 40))
102
+ canvas.paste(draw_boxes(img_orig.copy(), dets_v11, COLORS["YOLOv11x"], "v11"), (w+10, 40))
103
+ canvas.paste(draw_boxes(img_orig.copy(), dets_v5, COLORS["YOLOv5x"], "v5"), (2*w+20, 40))
 
 
 
 
 
 
 
 
104
  return canvas
105
 
106
+ # ── Main detect fn ────────────────────────────────────────────
107
+ def detect(image, model_choice, conf_thr, iou_thr):
 
 
 
 
 
 
108
  if image is None:
109
+ return None, "⚠️ Upload an image first."
 
110
  img_np = np.array(image.convert("RGB"))
111
+ t0 = time.time()
112
+ dets_v11, dets_v5, lines = [], [], []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ if model_choice in ("YOLOv11x", "Both"):
115
+ t1 = time.time(); dets_v11 = run_v11(img_np, conf_thr, iou_thr)
116
+ lines.append(f"**YOLOv11x** β€” {len(dets_v11)} detection(s) Β· {(time.time()-t1)*1000:.0f} ms")
 
 
 
 
 
 
 
 
 
117
 
118
+ if model_choice in ("YOLOv5x", "Both"):
119
+ t1 = time.time(); dets_v5 = run_v5(img_np, conf_thr, iou_thr)
120
+ lines.append(f"**YOLOv5x** β€” {len(dets_v5)} detection(s) Β· {(time.time()-t1)*1000:.0f} ms")
121
+
122
+ lines.append(f"⏱ {(time.time()-t0)*1000:.0f} ms total · {DEVICE.upper()}")
123
 
124
  if model_choice == "YOLOv11x":
125
  out = draw_boxes(image.copy(), dets_v11, COLORS["YOLOv11x"], "v11")
 
128
  else:
129
  out = side_by_side(image, dets_v11, dets_v5)
130
 
131
+ return out, "\n\n".join(lines)
132
+
133
+ # ── Custom CSS ────────────────────────────────────────────────
134
+ CSS = """
135
+ @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@400;500&display=swap');
136
 
137
+ body, .gradio-container {
138
+ background: #0f0f0f !important;
139
+ color: #e8e8e8 !important;
140
+ font-family: 'DM Sans', sans-serif !important;
141
+ }
142
+
143
+ #main-title {
144
+ font-family: 'Bebas Neue', sans-serif !important;
145
+ font-size: clamp(3rem, 8vw, 6rem) !important;
146
+ letter-spacing: 0.06em;
147
+ color: #ffffff;
148
+ text-align: center;
149
+ padding: 2rem 0 0.5rem 0;
150
+ line-height: 1;
151
+ border-bottom: 2px solid #f0a500;
152
+ margin-bottom: 2rem;
153
+ }
154
+
155
+ /* Panels */
156
+ .gr-box, .gr-panel, .svelte-1gfkn6j, .block {
157
+ background: #1a1a1a !important;
158
+ border: 1px solid #2a2a2a !important;
159
+ border-radius: 6px !important;
160
+ }
161
+
162
+ /* Radio buttons */
163
+ .gr-radio-row label {
164
+ background: #222 !important;
165
+ border: 1px solid #333 !important;
166
+ border-radius: 4px !important;
167
+ padding: 6px 14px !important;
168
+ cursor: pointer;
169
+ transition: all 0.15s;
170
+ }
171
+ .gr-radio-row label:hover { border-color: #f0a500 !important; }
172
+ .gr-radio-row input:checked + label,
173
+ .gr-radio-row label[data-checked] {
174
+ border-color: #f0a500 !important;
175
+ color: #f0a500 !important;
176
+ }
177
 
178
+ /* Sliders */
179
+ input[type=range] { accent-color: #f0a500; }
180
+
181
+ /* Detect button */
182
+ #detect-btn {
183
+ background: #f0a500 !important;
184
+ color: #0f0f0f !important;
185
+ font-family: 'Bebas Neue', sans-serif !important;
186
+ font-size: 1.3rem !important;
187
+ letter-spacing: 0.1em;
188
+ border: none !important;
189
+ border-radius: 4px !important;
190
+ height: 52px !important;
191
+ transition: opacity 0.2s;
192
+ }
193
+ #detect-btn:hover { opacity: 0.85; }
194
 
195
+ /* Labels */
196
+ label span, .gr-label { color: #999 !important; font-size: 0.78rem !important; text-transform: uppercase; letter-spacing: 0.08em; }
 
 
 
197
 
198
+ /* Example thumbnails */
199
+ .gr-samples-table img { border-radius: 4px; border: 1px solid #333; }
200
 
201
+ /* Stats output */
202
+ #stats-out { font-size: 0.85rem !important; color: #aaa !important; }
203
  """
204
 
205
+ # ── UI ────────────────────────────────────────────────────────
206
+ with gr.Blocks(title="Mudflap Detector", css=CSS) as demo:
207
 
208
+ gr.HTML('<div id="main-title">Mudflap Detector</div>')
209
+
210
+ with gr.Row(equal_height=False):
211
+
212
+ # ── Left column: inputs ───────────────────────────────
213
+ with gr.Column(scale=1, min_width=300):
214
+ inp_image = gr.Image(type="pil", label="Input Image", height=300)
215
+
216
+ gr.HTML('<div style="font-size:0.72rem;color:#666;text-transform:uppercase;letter-spacing:.08em;margin:10px 0 6px">Example Images</div>')
217
+ gr.Examples(
218
+ examples=[
219
+ ["examples/example1.jpg", "Both", 0.60, 0.45],
220
+ ["examples/example2.jpg", "YOLOv11x", 0.60, 0.45],
221
+ ],
222
+ inputs=[inp_image,
223
+ gr.State("Both"), # placeholder β€” wired below
224
+ gr.State(0.60),
225
+ gr.State(0.45)],
226
+ label=None,
227
  )
228
+
229
+ model_sel = gr.Radio(
230
+ choices=["YOLOv11x", "YOLOv5x", "Both"],
231
+ value="Both",
232
+ label="Model",
233
  )
 
234
 
235
+ with gr.Row():
236
+ conf_slider = gr.Slider(0.10, 0.95, value=0.60, step=0.05, label="Confidence")
237
+ iou_slider = gr.Slider(0.10, 0.90, value=0.45, step=0.05, label="IoU")
238
+
239
+ run_btn = gr.Button("DETECT", elem_id="detect-btn", variant="primary")
240
+
241
+ # ── Right column: output ──────────────────────────────
242
+ with gr.Column(scale=2, min_width=400):
243
+ out_image = gr.Image(type="pil", label="Result", height=500)
244
+ out_stats = gr.Markdown(elem_id="stats-out")
245
 
246
  run_btn.click(
247
  fn=detect,
 
249
  outputs=[out_image, out_stats],
250
  )
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  if __name__ == "__main__":
253
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)