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")