File size: 5,867 Bytes
105944b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from ultralytics import YOLO
import numpy as np
from PIL import Image
import io
import json
import os
import logging
import threading
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
app = FastAPI(title="Plastic Detection API", version="1.0.0")
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Global model state
model = None
model_loading = False
model_ready = False
def preload_model():
"""Load model in background thread"""
global model, model_loading, model_ready
if model is None and not model_loading:
model_loading = True
logger.info("๐ Pre-loading YOLO model in background...")
try:
model_path = "runs/plastic_detection/weights/best.pt"
if not os.path.exists(model_path):
logger.warning(f"Model not found: {model_path}, using yolov8n.pt")
model_path = "yolov8n.pt"
model = YOLO(model_path)
model_ready = True
logger.info(f"โ
Model loaded: {model_path}")
except Exception as e:
logger.error(f"โ Failed to load model: {e}")
model_loading = False
def get_model():
"""Get model, load if needed"""
global model, model_loading, model_ready
if model is None:
if not model_loading:
preload_model()
# Wait for model to load (with timeout)
timeout = 120 # 2 minutes
waited = 0
while model is None and waited < timeout:
import time
time.sleep(0.5)
waited += 0.5
if model is None:
raise RuntimeError("Model failed to load")
return model
@app.on_event("startup")
async def startup_event():
"""Pre-load model on startup"""
logger.info("๐ Starting Plastic Detection API...")
# Load model in background thread
thread = threading.Thread(target=preload_model, daemon=True)
thread.start()
@app.get("/")
async def root():
"""Health check endpoint"""
return {
"message": "Plastic Detection API is running",
"status": "ok",
"model_ready": model_ready
}
@app.post("/predict")
@app.post("/Predict")
async def predict(file: UploadFile = File(None)):
"""
Predict plastic type in an image
Args:
file: Image file (multipart form upload)
Returns:
JSON with predictions
"""
logger.info(f"๐ฅ Prediction request received, model_ready={model_ready}")
try:
if not file:
raise HTTPException(status_code=400, detail="No file provided")
model_instance = get_model()
# Read image file
contents = await file.read()
image = Image.open(io.BytesIO(contents))
logger.info(f"โ
Image loaded: {image.size}")
# Run inference
logger.info("๐ฎ Running YOLO inference...")
results = model_instance.predict(source=image, conf=0.25, verbose=False)
# Extract predictions
predictions = []
if results and len(results) > 0:
result = results[0]
class_names = result.names
if result.boxes is not None and len(result.boxes) > 0:
for box in result.boxes:
pred = {
"class": class_names[int(box.cls)],
"class_id": int(box.cls),
"confidence": float(box.conf),
"bbox": {
"x1": float(box.xyxy[0][0]),
"y1": float(box.xyxy[0][1]),
"x2": float(box.xyxy[0][2]),
"y2": float(box.xyxy[0][3]),
}
}
predictions.append(pred)
logger.info(f"โ
Found {len(predictions)} detections")
return {
"success": True,
"detections": predictions,
"count": len(predictions),
"model": "YOLOv8 Best",
"filename": file.filename
}
except Exception as e:
logger.error(f"โ Error: {str(e)}", exc_info=True)
raise HTTPException(status_code=400, detail=f"Error processing image: {str(e)}")
@app.get("/classes")
async def get_classes():
"""Get all detectable classes"""
logger.info("Classes request")
try:
model_instance = get_model()
classes = model_instance.names
logger.info(f"Returning {len(classes)} classes")
return {
"classes": classes,
"total": len(classes)
}
except Exception as e:
logger.error(f"Error: {str(e)}", exc_info=True)
raise HTTPException(status_code=400, detail=f"Error: {str(e)}")
@app.get("/stats")
async def get_stats():
"""Get backend statistics"""
return {
"status": "running",
"model_ready": model_ready,
"model_loading": model_loading,
"available_models": ["best.pt", "yolov8n.pt"]
}
if __name__ == "__main__":
print("๐ Starting Plastic Detection API...")
print("๐ Available at: http://0.0.0.0:8000")
print("๐ Docs: http://localhost:8000/docs")
print("โณ Model loads on first request...")
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
|