mohammed-aljafry commited on
Commit
ff44cfa
·
verified ·
1 Parent(s): f016809

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +468 -0
app.py ADDED
@@ -0,0 +1,468 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - InterFuser Self-Driving API Server
2
+
3
+ import uuid
4
+ import base64
5
+ import cv2
6
+ import torch
7
+ import numpy as np
8
+ from fastapi import FastAPI, HTTPException
9
+ from fastapi.responses import HTMLResponse
10
+ from pydantic import BaseModel
11
+ from torchvision import transforms
12
+ from typing import List, Dict, Any, Optional
13
+ import logging
14
+ import uuid
15
+ import base64
16
+ import cv2
17
+ import torch
18
+ import numpy as np
19
+ import logging
20
+ from fastapi import FastAPI, HTTPException
21
+ from fastapi.responses import HTMLResponse
22
+ from pydantic import BaseModel, Field
23
+ from typing import List, Dict, Tuple
24
+
25
+ from model_definition import InterfuserModel, load_and_prepare_model, get_master_config
26
+
27
+
28
+ from simulation_modules import InterfuserController, Tracker
29
+ from simulation_modules import DisplayInterface, render_bev, unnormalize_image, DisplayConfig
30
+
31
+ # ==============================================================================
32
+ # 2. إعدادات عامة وتطبيق FastAPI
33
+ # ==============================================================================
34
+ # إعداد التسجيل (Logging)
35
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
36
+
37
+ # تهيئة تطبيق FastAPI
38
+ app = FastAPI(
39
+ title="Baseer Self-Driving API",
40
+ description="An advanced API for the InterFuser self-driving model, providing real-time control commands and scene analysis.",
41
+ version="1.1.0"
42
+ )
43
+
44
+ # متغيرات عامة سيتم تهيئتها عند بدء التشغيل
45
+ MODEL: InterfuserModel = None
46
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
47
+ SESSIONS: Dict[str, Dict] = {} # قاموس لتخزين حالة الجلسات النشطة
48
+
49
+ # ==============================================================================
50
+ # 3. تعريف نماذج البيانات (Pydantic Models) للـ API
51
+ # ==============================================================================
52
+ class Measurements(BaseModel):
53
+ pos_global: Tuple[float, float] = Field(..., example=(0.0, 0.0), description="Global [X, Y] position of the vehicle.")
54
+ theta: float = Field(..., example=0.0, description="Global orientation angle of the vehicle in radians.")
55
+ speed: float = Field(..., example=0.0, description="Current speed in m/s.")
56
+ target_point: Tuple[float, float] = Field(..., example=(10.0, 0.0), description="Target point relative to the vehicle.")
57
+
58
+ class RunStepRequest(BaseModel):
59
+ session_id: str
60
+ image_b64: str = Field(..., description="Base64 encoded string of the vehicle's front camera view (BGR format).")
61
+ measurements: Measurements
62
+
63
+ class ControlCommands(BaseModel):
64
+ steer: float
65
+ throttle: float
66
+ brake: bool
67
+
68
+ class SceneAnalysis(BaseModel):
69
+ is_junction: float
70
+ traffic_light_state: float
71
+ stop_sign: float
72
+
73
+ class RunStepResponse(BaseModel):
74
+ control_commands: ControlCommands
75
+ scene_analysis: SceneAnalysis
76
+ predicted_waypoints: List[Tuple[float, float]]
77
+ dashboard_b64: str = Field(..., description="Base64 encoded string of the comprehensive dashboard view.")
78
+ reason: str = Field(..., description="The reason for the current control action (e.g., 'Following ID 12', 'Red Light').")
79
+
80
+ # ==============================================================================
81
+ # 4. دوال مساعدة (Helpers)
82
+ # ==============================================================================
83
+ def b64_to_cv2(b64_string: str) -> np.ndarray:
84
+ try:
85
+ img_bytes = base64.b64decode(b64_string)
86
+ img_array = np.frombuffer(img_bytes, dtype=np.uint8)
87
+ return cv2.imdecode(img_array, cv2.IMREAD_COLOR)
88
+ except Exception:
89
+ raise HTTPException(status_code=400, detail="Invalid Base64 image string.")
90
+
91
+ def cv2_to_b64(img: np.ndarray) -> str:
92
+ _, buffer = cv2.imencode('.jpg', img)
93
+ return base64.b64encode(buffer).decode('utf-8')
94
+
95
+ def prepare_model_input(image: np.ndarray, measurements: Measurements) -> Dict[str, torch.Tensor]:
96
+ """
97
+ إعداد دفعة (batch of 1) لتمريرها إلى النموذج.
98
+ """
99
+ transform = transforms.Compose([
100
+ transforms.ToTensor(),
101
+ transforms.Resize((224, 224), antialias=True),
102
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
103
+ ])
104
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
105
+ image_tensor = transform(image_rgb).unsqueeze(0).to(DEVICE)
106
+
107
+ measurements_tensor = torch.tensor([[
108
+ measurements.pos_global[0], measurements.pos_global[1], measurements.theta,
109
+ 0.0, 0.0, 0.0, # Steer, throttle, brake (not used by model)
110
+ measurements.speed, 4.0 # Command (default to FollowLane)
111
+ ]], dtype=torch.float32).to(DEVICE)
112
+
113
+ target_point_tensor = torch.tensor([measurements.target_point], dtype=torch.float32).to(DEVICE)
114
+
115
+ return {
116
+ 'rgb': image_tensor,
117
+ 'rgb_left': image_tensor.clone(), 'rgb_right': image_tensor.clone(), 'rgb_center': image_tensor.clone(),
118
+ 'measurements': measurements_tensor,
119
+ 'target_point': target_point_tensor,
120
+ 'lidar': torch.zeros_like(image_tensor)
121
+ }
122
+
123
+ # ==============================================================================
124
+ # 5. أحداث دورة حياة التطبيق (Startup/Shutdown)
125
+ # ==============================================================================
126
+ @app.on_event("startup")
127
+ async def startup_event():
128
+ global MODEL
129
+ logging.info("🚗 Server starting up...")
130
+ logging.info(f"Using device: {DEVICE}")
131
+ MODEL = load_and_prepare_model(DEVICE)
132
+ if MODEL:
133
+ logging.info("✅ Model loaded successfully. Server is ready!")
134
+ else:
135
+ logging.error("❌ CRITICAL: Model could not be loaded. The API will not function correctly.")
136
+
137
+ # ==============================================================================
138
+ # 6. نقاط النهاية الرئيسية (API Endpoints)
139
+ # ==============================================================================
140
+
141
+ @app.get("/", response_class=HTMLResponse, include_in_schema=False, tags=["General"])
142
+ async def root():
143
+ """
144
+ [النسخة النهائية مع التمرير]
145
+ يعرض صفحة رئيسية احترافية وجذابة بصريًا مع تمكين التمرير العمودي.
146
+ """
147
+ active_sessions_count = len(SESSIONS)
148
+
149
+ status_color = "#00ff7f" # SpringGreen
150
+ status_text = "متصل ويعمل"
151
+ if MODEL is None:
152
+ status_color = "#ff4757" # Red
153
+ status_text = "خطأ: النموذج غير متاح"
154
+
155
+ html_content = f"""
156
+ <!DOCTYPE html>
157
+ <html dir="rtl" lang="ar">
158
+ <head>
159
+ <meta charset="UTF-8">
160
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
161
+ <title>🚗 Baseer - واجهة القيادة الذاتية</title>
162
+ <style>
163
+ @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;700;900&display=swap');
164
+
165
+ :root {{
166
+ --bg-dark: #1a1a2e;
167
+ --panel-dark: #16213e;
168
+ --primary-accent: #0f3460;
169
+ --secondary-accent: #e94560;
170
+ --glow-color: #537895;
171
+ --text-light: #e0e0e0;
172
+ --text-header: #ffffff;
173
+ }}
174
+
175
+ body {{
176
+ font-family: 'Cairo', sans-serif;
177
+ background: var(--bg-dark);
178
+ background-image: linear-gradient(to right top, #1a1a2e, #1c1d32, #1f2037, #22233b, #252640);
179
+ color: var(--text-light);
180
+ margin: 0;
181
+ padding: 40px 20px; /* إضافة padding علوي وسفلي للسماح بالمساحة */
182
+ min-height: 100vh;
183
+ box-sizing: border-box; /* لضمان أن padding لا يضيف إلى الارتفاع */
184
+
185
+ /* --- [التصحيح هنا] --- */
186
+ overflow-x: hidden; /* إخفاء التمرير الأفقي غير المرغوب فيه */
187
+ overflow-y: auto; /* السماح بالتمرير العمودي عند الحاجة */
188
+ }}
189
+
190
+ .main-content {{
191
+ display: flex;
192
+ justify-content: center;
193
+ align-items: center;
194
+ width: 100%;
195
+ }}
196
+
197
+ .container {{
198
+ background: rgba(22, 33, 62, 0.85);
199
+ backdrop-filter: blur(15px);
200
+ border-radius: 25px;
201
+ padding: 40px 50px;
202
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
203
+ text-align: center;
204
+ max-width: 750px;
205
+ width: 100%;
206
+ border: 1px solid rgba(255, 255, 255, 0.1);
207
+ position: relative;
208
+ z-index: 1;
209
+ }}
210
+
211
+ /* ... باقي كود CSS يبقى كما هو تمامًا ... */
212
+ .logo {{
213
+ font-size: 5rem;
214
+ margin-bottom: 10px;
215
+ text-shadow: 0 0 15px var(--glow-color);
216
+ animation: float 4s ease-in-out infinite;
217
+ }}
218
+
219
+ @keyframes float {{
220
+ 0% {{ transform: translateY(0px); }}
221
+ 50% {{ transform: translateY(-15px); }}
222
+ 100% {{ transform: translateY(0px); }}
223
+ }}
224
+
225
+ h1 {{
226
+ font-size: 3rem;
227
+ font-weight: 900;
228
+ color: var(--text-header);
229
+ margin-bottom: 5px;
230
+ letter-spacing: 1px;
231
+ }}
232
+
233
+ .subtitle {{
234
+ font-size: 1.3rem;
235
+ color: #a7a9be;
236
+ margin-bottom: 25px;
237
+ }}
238
+
239
+ .status-badge {{
240
+ display: inline-flex;
241
+ align-items: center;
242
+ gap: 10px;
243
+ background-color: rgba(255, 255, 255, 0.05);
244
+ border: 1px solid {status_color};
245
+ color: {status_color};
246
+ padding: 10px 22px;
247
+ border-radius: 50px;
248
+ font-weight: bold;
249
+ margin-bottom: 35px;
250
+ font-size: 1.1rem;
251
+ box-shadow: 0 0 15px {status_color}33;
252
+ }}
253
+
254
+ .stats-grid {{
255
+ display: grid;
256
+ grid-template-columns: 1fr 1fr;
257
+ gap: 25px;
258
+ margin-bottom: 40px;
259
+ }}
260
+
261
+ .stat-card {{
262
+ background: var(--primary-accent);
263
+ padding: 25px;
264
+ border-radius: 20px;
265
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
266
+ }}
267
+
268
+ .stat-card:hover {{
269
+ transform: scale(1.05);
270
+ box-shadow: 0 0 25px var(--secondary-accent)44;
271
+ }}
272
+
273
+ .stat-number {{
274
+ font-size: 3rem;
275
+ font-weight: 700;
276
+ color: var(--secondary-accent);
277
+ }}
278
+
279
+ .stat-label {{
280
+ font-size: 1rem;
281
+ color: #a7a9be;
282
+ margin-top: 5px;
283
+ }}
284
+
285
+ .button-group {{
286
+ display: flex;
287
+ gap: 20px;
288
+ justify-content: center;
289
+ }}
290
+
291
+ .btn {{
292
+ padding: 15px 35px;
293
+ border-radius: 50px;
294
+ text-decoration: none;
295
+ font-weight: 700;
296
+ font-size: 1.1rem;
297
+ transition: all 0.3s ease;
298
+ border: none;
299
+ cursor: pointer;
300
+ position: relative;
301
+ overflow: hidden;
302
+ }}
303
+
304
+ .btn-primary {{
305
+ background: var(--secondary-accent);
306
+ color: var(--text-header);
307
+ box-shadow: 0 5px 15px {status_color}44;
308
+ }}
309
+
310
+ .btn-primary:hover {{
311
+ box-shadow: 0 8px 25px {status_color}66;
312
+ transform: translateY(-3px);
313
+ }}
314
+
315
+ .btn-secondary {{
316
+ background: transparent;
317
+ color: var(--text-light);
318
+ border: 2px solid var(--glow-color);
319
+ }}
320
+
321
+ .btn-secondary:hover {{
322
+ background: var(--glow-color);
323
+ color: var(--text-header);
324
+ border-color: var(--glow-color);
325
+ }}
326
+
327
+ </style>
328
+ </head>
329
+ <body>
330
+ <div class="main-content">
331
+ <div class="container">
332
+ <div class="logo">🚀</div>
333
+ <h1>بصيـر API</h1>
334
+ <p class="subtitle">مستقبل القيادة الذاتية بين يديك</p>
335
+
336
+ <div class="status-badge">
337
+ <span style="width: 12px; height: 12px; background-color: {status_color}; border-radius: 50%;"></span>
338
+ <span>{status_text}</span>
339
+ </div>
340
+
341
+ <div class="stats-grid">
342
+ <div class="stat-card">
343
+ <div class="stat-number">{active_sessions_count}</div>
344
+ <div class="stat-label">الجلسات النشطة</div>
345
+ </div>
346
+ <div class="stat-card">
347
+ <div class="stat-number">1.1</div>
348
+ <div class="stat-label">الإصدار</div>
349
+ </div>
350
+ </div>
351
+
352
+ <div class="button-group">
353
+ <a href="/docs" target="_blank" class="btn btn-primary">📚 التوثيق التفاعلي</a>
354
+ <a href="/sessions" target="_blank" class="btn btn-secondary">📊 عرض الجلسات</a>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </body>
359
+ </html>
360
+ """
361
+ return HTMLResponse(content=html_content)
362
+
363
+ @app.post("/start_session", summary="Start a new driving session", tags=["Session Management"])
364
+ def start_session():
365
+ session_id = str(uuid.uuid4())
366
+
367
+ # 1. الحصول على الإعدادات الكاملة من المصدر الوحيد
368
+ config = get_master_config()
369
+
370
+ # 2. استخراج الإعدادات المطلوبة لكل مكون بشكل صريح
371
+ grid_conf = config['grid_conf']
372
+ controller_params = config['controller_params']
373
+ simulation_freq = config['simulation']['frequency']
374
+
375
+ # 3. تهيئة المتتبع (Tracker) بمعلماته المحددة
376
+ tracker = Tracker(
377
+ grid_conf=grid_conf,
378
+ match_threshold=controller_params.get('tracker_match_thresh', 2.5),
379
+ prune_age=controller_params.get('tracker_prune_age', 5)
380
+ )
381
+
382
+ # 4. تهيئة المتحكم (Controller)
383
+ controller = InterfuserController({
384
+ 'controller_params': controller_params,
385
+ 'grid_conf': grid_conf,
386
+ 'frequency': simulation_freq
387
+ })
388
+
389
+ # 5. إنشاء الجلسة بالكائنات المهيأة
390
+ SESSIONS[session_id] = {
391
+ 'tracker': tracker,
392
+ 'controller': controller,
393
+ 'frame_num': 0
394
+ }
395
+
396
+ logging.info(f"New session started: {session_id}")
397
+ return {"session_id": session_id}
398
+
399
+ @app.post("/run_step", response_model=RunStepResponse, summary="Process a single simulation step", tags=["Core"])
400
+ @torch.no_grad()
401
+ def run_step(request: RunStepRequest):
402
+ if MODEL is None:
403
+ raise HTTPException(status_code=503, detail="Model is not available.")
404
+
405
+ session = SESSIONS.get(request.session_id)
406
+ if not session:
407
+ raise HTTPException(status_code=404, detail="Session ID not found.")
408
+
409
+ # --- 1. الإدراك (Perception) ---
410
+ image = b64_to_cv2(request.image_b64)
411
+ model_input = prepare_model_input(image, request.measurements)
412
+ traffic, waypoints, junc, light, stop, _ = MODEL(model_input)
413
+
414
+ # --- 2. معالجة مخرجات النموذج ---
415
+ traffic_processed = torch.cat([torch.sigmoid(traffic[0][:, 0:1]), traffic[0][:, 1:]], dim=1)
416
+ traffic_np = traffic_processed.cpu().numpy().reshape(20, 20, -1)
417
+ waypoints_np = waypoints[0].cpu().numpy()
418
+ junction_prob = torch.softmax(junc, dim=1)[0, 1].item()
419
+ light_prob = torch.softmax(light, dim=1)[0, 1].item()
420
+ stop_prob = torch.softmax(stop, dim=1)[0, 1].item()
421
+
422
+ # --- 3. التتبع والتحكم ---
423
+ ego_pos = np.array(request.measurements.pos_global)
424
+ ego_theta = request.measurements.theta
425
+ frame_num = session['frame_num']
426
+
427
+ active_tracks = session['tracker'].process_frame(traffic_np, ego_pos, ego_theta, frame_num)
428
+ steer, throttle, brake, ctrl_info = session['controller'].run_step(
429
+ speed=request.measurements.speed, waypoints=torch.from_numpy(waypoints_np),
430
+ junction=junction_prob, traffic_light=light_prob, stop_sign=stop_prob,
431
+ bev_map=traffic_np, ego_pos=ego_pos, ego_theta=ego_theta, frame_num=frame_num
432
+ )
433
+
434
+ # --- 4. إنشاء الواجهة المرئية ---
435
+ display_iface = DisplayInterface(DisplayConfig(width=1280, height=720))
436
+ bev_maps = render_bev(active_tracks, waypoints_np, ego_pos, ego_theta)
437
+ display_data = {
438
+ 'camera_view': image, 'map_t0': bev_maps['t0'], 'map_t1': bev_maps['t1'], 'map_t2': bev_maps['t2'],
439
+ 'frame_num': frame_num, 'speed': request.measurements.speed * 3.6,
440
+ 'target_speed': ctrl_info.get('target_speed', 0) * 3.6,
441
+ 'steer': steer, 'throttle': throttle, 'brake': brake,
442
+ 'light_prob': light_prob, 'stop_prob': stop_prob,
443
+ 'object_counts': {'car': len(active_tracks)}
444
+ }
445
+ dashboard = display_iface.run_interface(display_data)
446
+
447
+ # --- 5. تحديث الجلسة وإرجاع الرد ---
448
+ session['frame_num'] += 1
449
+
450
+ return RunStepResponse(
451
+ control_commands=ControlCommands(steer=steer, throttle=throttle, brake=brake),
452
+ scene_analysis=SceneAnalysis(is_junction=junction_prob, traffic_light_state=light_prob, stop_sign=stop_prob),
453
+ predicted_waypoints=[tuple(wp) for wp in waypoints_np.tolist()],
454
+ dashboard_b64=cv2_to_b64(dashboard),
455
+ reason=ctrl_info.get('brake_reason', "Cruising")
456
+ )
457
+
458
+ @app.post("/end_session", summary="End and clean up a session", tags=["Session Management"])
459
+ def end_session(session_id: str):
460
+ if session_id in SESSIONS:
461
+ del SESSIONS[session_id]
462
+ logging.info(f"Session ended: {session_id}")
463
+ return {"message": f"Session {session_id} ended."}
464
+ raise HTTPException(status_code=404, detail="Session not found.")
465
+ # ================== تشغيل الخادم ==================
466
+ if __name__ == "__main__":
467
+ import uvicorn
468
+ uvicorn.run(app, host="0.0.0.0", port=7860)