aayanb09 commited on
Commit
b020405
Β·
verified Β·
1 Parent(s): 6c20cec

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +260 -0
app.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ from pathlib import Path
5
+ from ultralytics import YOLO
6
+
7
+ # ── Constants ─────────────────────────────────────────────────────────────────
8
+ MODEL_PATH = "yolov8_cattle_keypoints.pt"
9
+
10
+ KP_NAMES = [
11
+ "left_ear_tip", "right_ear_tip", "left_ear_base", "right_ear_base",
12
+ "left_eye", "right_eye", "nose_left", "nose_right", "nose_tip",
13
+ "mouth_left", "mouth_right", "chin_left", "chin_right",
14
+ ]
15
+
16
+ KP_COLORS = [
17
+ (255, 69, 0),
18
+ ( 30, 144, 255),
19
+ (255, 165, 0),
20
+ ( 0, 191, 255),
21
+ (154, 205, 50),
22
+ (238, 130, 238),
23
+ ( 0, 255, 127),
24
+ (255, 215, 0),
25
+ (255, 255, 0),
26
+ (255, 20, 147),
27
+ ( 0, 255, 255),
28
+ (255, 140, 0),
29
+ (147, 112, 219),
30
+ ]
31
+
32
+ SKELETON = [
33
+ (0, 2), (1, 3),
34
+ (2, 4), (3, 5),
35
+ (4, 5),
36
+ (6, 8), (7, 8),
37
+ (6, 9), (7, 10),
38
+ (9, 10),
39
+ (9, 11), (10, 12),
40
+ (11, 12),
41
+ (4, 6), (5, 7),
42
+ ]
43
+
44
+ # ── Model loading ─────────────────────────────────────────────────────────────
45
+ _model = None
46
+
47
+ def load_model():
48
+ global _model
49
+ if _model is None:
50
+ if not Path(MODEL_PATH).exists():
51
+ raise FileNotFoundError(
52
+ f"Model file '{MODEL_PATH}' not found. "
53
+ "Upload yolov8_cattle_keypoints.pt to the Space root."
54
+ )
55
+ _model = YOLO(MODEL_PATH)
56
+ return _model
57
+
58
+
59
+ # ── Inference ─────────────────────────────────────────────────────────────────
60
+ def run_inference(image: np.ndarray, conf_threshold: float, show_labels: bool):
61
+ if image is None:
62
+ return None, "Upload an image first."
63
+
64
+ m = load_model()
65
+ results = m.predict(source=image, conf=float(conf_threshold), verbose=False)
66
+
67
+ annotated = image.copy()
68
+ table_rows = []
69
+
70
+ for result in results:
71
+ boxes = result.boxes
72
+ keypoints_data = result.keypoints
73
+ if boxes is None or keypoints_data is None or len(boxes) == 0:
74
+ continue
75
+
76
+ for det_idx in range(len(boxes)):
77
+ conf = float(boxes.conf[det_idx])
78
+ kps = keypoints_data.data[det_idx].cpu().numpy() # (13, 3)
79
+
80
+ # Bounding box
81
+ x1, y1, x2, y2 = boxes.xyxy[det_idx].cpu().numpy().astype(int)
82
+ cv2.rectangle(annotated, (x1, y1), (x2, y2), (255, 255, 255), 2)
83
+ cv2.putText(
84
+ annotated, f"cattle {conf:.2f}",
85
+ (x1, max(y1 - 8, 0)),
86
+ cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1, cv2.LINE_AA,
87
+ )
88
+
89
+ # Skeleton
90
+ for (i, j) in SKELETON:
91
+ if i >= len(kps) or j >= len(kps):
92
+ continue
93
+ xi, yi, vi = kps[i]
94
+ xj, yj, vj = kps[j]
95
+ if vi < 0.5 or vj < 0.5:
96
+ continue
97
+ cv2.line(
98
+ annotated,
99
+ (int(xi), int(yi)), (int(xj), int(yj)),
100
+ (180, 180, 180), 1, cv2.LINE_AA,
101
+ )
102
+
103
+ # Keypoints
104
+ row = {"detection": det_idx + 1, "confidence": f"{conf:.3f}"}
105
+ for kp_idx, (kx, ky, kv) in enumerate(kps):
106
+ name = KP_NAMES[kp_idx]
107
+ color = KP_COLORS[kp_idx]
108
+ if kv > 0.5:
109
+ cv2.circle(annotated, (int(kx), int(ky)), 6, color, -1)
110
+ cv2.circle(annotated, (int(kx), int(ky)), 7, (0, 0, 0), 1)
111
+ if show_labels:
112
+ cv2.putText(
113
+ annotated, name,
114
+ (int(kx) + 8, int(ky) - 4),
115
+ cv2.FONT_HERSHEY_SIMPLEX, 0.38,
116
+ color, 1, cv2.LINE_AA,
117
+ )
118
+ row[name] = f"({int(kx)}, {int(ky)}) vis={kv:.2f}"
119
+ else:
120
+ row[name] = "not visible"
121
+
122
+ table_rows.append(row)
123
+
124
+ if table_rows:
125
+ lines = [f"### {len(table_rows)} detection(s) found\n"]
126
+ for row in table_rows:
127
+ lines.append(f"**Detection {row['detection']}** β€” conf {row['confidence']}")
128
+ for name in KP_NAMES:
129
+ lines.append(f" - `{name}`: {row.get(name, 'n/a')}")
130
+ lines.append("")
131
+ results_md = "\n".join(lines)
132
+ else:
133
+ results_md = "### No cattle detected\nTry lowering the confidence threshold."
134
+
135
+ return annotated, results_md
136
+
137
+
138
+ # ── CSS ────────────────────────────────────────────────────────────────���──────
139
+ CSS = """
140
+ @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap');
141
+
142
+ :root {
143
+ --bg: #0d0f0e;
144
+ --surface: #161a18;
145
+ --border: #2a332e;
146
+ --accent: #4ffe9a;
147
+ --muted: #7a8c80;
148
+ --text: #e8ede9;
149
+ --radius: 4px;
150
+ }
151
+ body, .gradio-container {
152
+ background: var(--bg) !important;
153
+ font-family: 'DM Sans', sans-serif !important;
154
+ color: var(--text) !important;
155
+ }
156
+ .hdr {
157
+ padding: 2.5rem 0 1.5rem;
158
+ text-align: center;
159
+ border-bottom: 1px solid var(--border);
160
+ margin-bottom: 2rem;
161
+ }
162
+ .hdr h1 {
163
+ font-family: 'Bebas Neue', sans-serif;
164
+ font-size: clamp(2.8rem, 7vw, 5.5rem);
165
+ letter-spacing: 0.08em;
166
+ color: var(--accent);
167
+ margin: 0;
168
+ line-height: 1;
169
+ text-shadow: 0 0 40px rgba(79,254,154,0.25);
170
+ }
171
+ .hdr p {
172
+ font-family: 'DM Mono', monospace;
173
+ font-size: 0.78rem;
174
+ color: var(--muted);
175
+ letter-spacing: 0.15em;
176
+ text-transform: uppercase;
177
+ margin: 0.6rem 0 0;
178
+ }
179
+ .tags {
180
+ display: flex; gap: 0.5rem; flex-wrap: wrap;
181
+ justify-content: center; margin-top: 0.8rem;
182
+ }
183
+ .tag {
184
+ font-family: 'DM Mono', monospace;
185
+ font-size: 0.68rem; letter-spacing: 0.1em;
186
+ text-transform: uppercase; padding: 0.25rem 0.65rem;
187
+ border: 1px solid var(--border); border-radius: 2px; color: var(--muted);
188
+ }
189
+ .tag.hot { border-color: var(--accent); color: var(--accent); }
190
+ button.primary {
191
+ background: var(--accent) !important;
192
+ color: #0d0f0e !important;
193
+ font-family: 'Bebas Neue', sans-serif !important;
194
+ font-size: 1.1rem !important;
195
+ letter-spacing: 0.12em !important;
196
+ border: none !important;
197
+ border-radius: var(--radius) !important;
198
+ padding: 0.7rem 2rem !important;
199
+ transition: opacity 0.15s, transform 0.1s !important;
200
+ }
201
+ button.primary:hover { opacity: 0.85 !important; transform: translateY(-1px) !important; }
202
+ input[type=range] { accent-color: var(--accent) !important; }
203
+ input[type=checkbox] { accent-color: var(--accent) !important; }
204
+ """
205
+
206
+ HEADER_HTML = """
207
+ <div class="hdr">
208
+ <h1>CattleFace Β· Pose</h1>
209
+ <p>YOLOv8 Β· 13-point facial landmark detection for bovines</p>
210
+ <div class="tags">
211
+ <span class="tag hot">13 keypoints</span>
212
+ <span class="tag">ears Β· eyes Β· nose Β· mouth Β· chin</span>
213
+ <span class="tag hot">real-time inference</span>
214
+ <span class="tag">UARK-AICV benchmark</span>
215
+ </div>
216
+ </div>
217
+ """
218
+
219
+ FOOTER_HTML = """
220
+ <div style="text-align:center;padding:1.5rem 0 0.5rem;
221
+ font-family:'DM Mono',monospace;font-size:0.7rem;
222
+ color:#7a8c80;letter-spacing:0.08em;">
223
+ MODEL Β· YOLOv8s-pose &nbsp;|&nbsp;
224
+ DATASET Β· UARK-AICV/CattleFace-RGBT-benchmark &nbsp;|&nbsp;
225
+ 13 FACIAL LANDMARKS
226
+ </div>
227
+ """
228
+
229
+ # ── Layout ────────────────────────────────────────────────────────────────────
230
+ with gr.Blocks(title="CattleFace Pose") as demo:
231
+ gr.HTML(HEADER_HTML)
232
+
233
+ with gr.Row():
234
+ with gr.Column(scale=1):
235
+ inp_image = gr.Image(
236
+ label="Input Image",
237
+ type="numpy",
238
+ sources=["upload", "webcam", "clipboard"],
239
+ )
240
+ conf_slider = gr.Slider(
241
+ minimum=0.05, maximum=0.95, value=0.25, step=0.05,
242
+ label="Confidence Threshold",
243
+ )
244
+ show_labels = gr.Checkbox(value=True, label="Show keypoint labels")
245
+ run_btn = gr.Button("Detect Landmarks", variant="primary")
246
+
247
+ with gr.Column(scale=1):
248
+ out_image = gr.Image(label="Annotated Output", type="numpy")
249
+ out_text = gr.Markdown(label="Keypoint Details")
250
+
251
+ run_btn.click(
252
+ fn=run_inference,
253
+ inputs=[inp_image, conf_slider, show_labels],
254
+ outputs=[out_image, out_text],
255
+ )
256
+
257
+ gr.HTML(FOOTER_HTML)
258
+
259
+ if __name__ == "__main__":
260
+ demo.launch(css=CSS)