Spaces:
Runtime error
Runtime error
| import cv2 | |
| import numpy as np | |
| import requests | |
| import torch | |
| import firebase_admin | |
| import os | |
| from fastapi import FastAPI, BackgroundTasks | |
| from pydantic import BaseModel | |
| from ultralytics import YOLO | |
| from firebase_admin import credentials, firestore | |
| # --- 0. THE "FORCE TRUST" SECURITY OVERRIDE --- | |
| # This stops the (y/N) prompt by forcing Torch Hub to trust all sub-repos | |
| import torch.hub | |
| # We redefine the internal check to always return True (TRUST EVERYTHING) | |
| torch.hub.trust_repo = lambda *args, **kwargs: True | |
| # Set environment variables for Hugging Face writable directories | |
| os.environ['TORCH_HOME'] = '/tmp/torch_cache' | |
| os.environ['YOLO_CONFIG_DIR'] = '/tmp/ultralytics_config' | |
| # --- 1. INITIALIZE MODELS --- | |
| app = FastAPI() | |
| def home(): | |
| return {"status": "Sahl Express AI is Online", "region": "Tunisia"} | |
| device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") | |
| print("🚀 Starting Sahl Express Engine...") | |
| # Load YOLOv8 (2D Segmentation) | |
| try: | |
| yolo_model = YOLO('best.pt') | |
| print("✅ YOLOv8 Loaded") | |
| except Exception as e: | |
| print(f"❌ YOLO Load Error: {e}") | |
| # Load MiDaS (Depth Estimation) | |
| try: | |
| print("📥 Loading MiDaS (Security Bypass Active)...") | |
| # Using 'trust_repo=True' alongside our override above | |
| midas = torch.hub.load("intel-isl/MiDaS", "MiDaS_small", trust_repo=True) | |
| midas_transforms = torch.hub.load("intel-isl/MiDaS", "transforms", trust_repo=True) | |
| midas.to(device) | |
| midas.eval() | |
| transform = midas_transforms.small_transform | |
| print("✅ MiDaS Loaded Successfully!") | |
| except Exception as e: | |
| print(f"❌ MiDaS Load Failed: {e}") | |
| # --- 2. FIREBASE SETUP --- | |
| try: | |
| # Ensure serviceAccount.json is uploaded to your HF Space Files tab | |
| cred = credentials.Certificate("serviceAccount.json") | |
| firebase_admin.initialize_app(cred) | |
| db = firestore.client() | |
| print("✅ Firebase Connected") | |
| except Exception as e: | |
| print(f"⚠️ Firebase Error: {e}") | |
| # Tunisian Reference Constants (cm) | |
| REFERENCE_SIZES = { | |
| 'reference_card': 8.56, # ID Card | |
| 'reference_paper': 21.0, # A4 Paper | |
| 'reference_coin': 2.8 # 1 Dinar | |
| } | |
| class ImageRequest(BaseModel): | |
| image_url: str | |
| delivery_id: str | |
| def get_depth_map(img): | |
| """ Converts an image to a relative depth map """ | |
| img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| input_batch = transform(img_rgb).to(device) | |
| with torch.no_grad(): | |
| prediction = midas(input_batch) | |
| prediction = torch.nn.functional.interpolate( | |
| prediction.unsqueeze(1), | |
| size=img.shape[:2], | |
| mode="bicubic", | |
| align_corners=False, | |
| ).squeeze() | |
| return prediction.cpu().numpy() | |
| def perform_3d_measurement(image_url: str, delivery_id: str): | |
| try: | |
| # Download Image from Cloudinary/URL | |
| resp = requests.get(image_url) | |
| img_array = np.asarray(bytearray(resp.content), dtype=np.uint8) | |
| img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) | |
| # A. Run AI Models | |
| yolo_results = yolo_model.predict(source=img, conf=0.4)[0] | |
| depth_map = get_depth_map(img) | |
| pixel_cm_ratio = None | |
| pkg_mask = None | |
| pkg_w_px, pkg_h_px = None, None | |
| # 1. Calibration: Find the reference object (e.g., Tunisian ID card) | |
| for i, box in enumerate(yolo_results.boxes): | |
| label = yolo_results.names[int(box.cls[0])] | |
| if label in REFERENCE_SIZES: | |
| x1, y1, x2, y2 = box.xyxy[0].tolist() | |
| pixel_cm_ratio = (x2 - x1) / REFERENCE_SIZES[label] | |
| break | |
| # 2. Identification: Find the Package and its mask | |
| for i, box in enumerate(yolo_results.boxes): | |
| label = yolo_results.names[int(box.cls[0])] | |
| if label == 'package' and yolo_results.masks is not None: | |
| pkg_mask = yolo_results.masks.xy[i] | |
| rect = cv2.minAreaRect(pkg_mask.astype(np.int32)) | |
| (_, _), (w, h), _ = rect | |
| pkg_w_px, pkg_h_px = w, h | |
| break | |
| # 3. 3D Volume Calculation | |
| if pixel_cm_ratio and pkg_w_px is not None: | |
| # Create a mask to sample depth data | |
| mask_img = np.zeros(depth_map.shape, dtype=np.uint8) | |
| cv2.fillPoly(mask_img, [pkg_mask.astype(np.int32)], 1) | |
| pkg_depth_val = np.median(depth_map[mask_img == 1]) | |
| # Ground depth (dilating the package mask to find the floor) | |
| kernel = np.ones((30,30), np.uint8) | |
| dilated = cv2.dilate(mask_img, kernel, iterations=2) | |
| ground_depth_val = np.median(depth_map[(dilated - mask_img) == 1]) | |
| # Convert Relative Depth to Real CM | |
| # TUNING_CONSTANT: 0.5 is a baseline; adjust after testing with real packages | |
| TUNING_CONSTANT = 0.5 | |
| depth_delta = abs(ground_depth_val - pkg_depth_val) | |
| real_h = round((depth_delta / pixel_cm_ratio) * TUNING_CONSTANT, 1) | |
| # Final 2D Dimensions | |
| real_w = round(pkg_w_px / pixel_cm_ratio, 1) | |
| real_l = round(pkg_h_px / pixel_cm_ratio, 1) | |
| if real_h < 0.5: real_h = 1.0 # Minimum thickness | |
| volume = round(real_w * real_l * real_h, 2) | |
| # 4. Update Firebase with the measured volume | |
| db.collection("orders").document(delivery_id).update({ | |
| "volume_cm3": volume, | |
| "dimensions": f"{real_l}x{real_w}x{real_h} cm", | |
| "status": "Measured_3D" | |
| }) | |
| print(f"📦 Success: {delivery_id} | Vol: {volume}cm3") | |
| except Exception as e: | |
| print(f"❌ Measurement Error: {e}") | |
| async def measure_endpoint(request: ImageRequest, background_tasks: BackgroundTasks): | |
| # This runs the heavy AI work in the background so the app doesn't freeze | |
| background_tasks.add_task(perform_3d_measurement, request.image_url, request.delivery_id) | |
| return {"status": "processing"} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| # Port 7860 is required for Hugging Face Spaces | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |