Spaces:
Runtime error
Runtime error
File size: 6,400 Bytes
cb170f7 | 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 | from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import uvicorn
import numpy as np
import cv2
import base64
import tempfile
import os
from ultralytics import YOLO
from PIL import Image
import io
# ββ Create the FastAPI app βββββββββββββββββββββββββββββββββββββββ
app = FastAPI(
title = "Polyp Detection API",
description = "YOLOv8 polyp detection and segmentation",
version = "1.0.0"
)
# ββ CORS Middleware ββββββββββββββββββββββββββββββββββββββββββββββ
# This allows your React app (running on a different port)
# to talk to this backend. Without this, browser blocks the request.
app.add_middleware(
CORSMiddleware,
allow_origins = ["*"], # in production, replace with your React URL
allow_credentials = True,
allow_methods = ["*"],
allow_headers = ["*"],
)
# ββ Load Model Once at Startup βββββββββββββββββββββββββββββββββββ
# We load the model once when server starts
# Not on every request β that would be very slow
MODEL_PATH = "polyp_best_model.pt"
print(f"Loading model from {MODEL_PATH}...")
model = YOLO(MODEL_PATH)
print("β
Model loaded successfully!")
# ββ Helper: Convert numpy image to base64 string ββββββββββββββββ
# React can't receive raw image data directly
# We convert it to a base64 string which React can display as <img>
def image_to_base64(img_rgb: np.ndarray) -> str:
_, buffer = cv2.imencode('.jpg', cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
img_bytes = buffer.tobytes()
img_b64 = base64.b64encode(img_bytes).decode('utf-8')
return f"data:image/jpeg;base64,{img_b64}"
# ββ Route 1: Health Check ββββββββββββββββββββββββββββββββββββββββ
# React will ping this first to confirm backend is alive
@app.get("/")
def health_check():
return {
"status" : "online",
"message" : "Polyp Detection API is running"
}
# ββ Route 2: Detect Polyps βββββββββββββββββββββββββββββββββββββββ
# This is the main route React sends images to
@app.post("/detect")
async def detect_polyps(file: UploadFile = File(...)):
"""
Receives an image file from React
Returns:
- annotated image (with masks drawn) as base64
- polyp count
- confidence scores
- original image as base64
"""
try:
# ββ Step 1: Read uploaded image ββββββββββββββββββββββββββ
contents = await file.read()
pil_image = Image.open(io.BytesIO(contents)).convert("RGB")
img_array = np.array(pil_image)
# ββ Step 2: Save to temp file (the fix that worked before)
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
img_resized = cv2.resize(img_bgr, (640, 640))
tmp = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
tmp_path = tmp.name
tmp.close()
cv2.imwrite(tmp_path, img_resized)
# ββ Step 3: Run YOLO model ββββββββββββββββββββββββββββββββ
results = model(
tmp_path,
conf = 0.15,
iou = 0.4,
verbose = False
)
os.remove(tmp_path)
# ββ Step 4: Draw results on image βββββββββββββββββββββββββ
annotated = results[0].plot(conf=True, masks=True, boxes=True)
annotated_rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
# ββ Step 5: Extract detection data βββββββββββββββββββββββ
boxes = results[0].boxes
detections = []
if boxes is not None and len(boxes) > 0:
for idx, box in enumerate(boxes):
conf = float(box.conf[0].cpu().numpy())
xyxy = box.xyxy[0].cpu().numpy().tolist()
detections.append({
"polyp_number" : idx + 1,
"confidence" : round(conf * 100, 1), # as percentage
"bbox" : {
"x1" : round(xyxy[0]),
"y1" : round(xyxy[1]),
"x2" : round(xyxy[2]),
"y2" : round(xyxy[3])
}
})
# ββ Step 6: Prepare original image for display ββββββββββββ
orig_resized = cv2.resize(
cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR),
(640, 640)
)
orig_rgb = cv2.cvtColor(orig_resized, cv2.COLOR_BGR2RGB)
# ββ Step 7: Return everything to React βββββββββββββββββββ
return JSONResponse(content={
"success" : True,
"polyp_count" : len(detections),
"detections" : detections,
"annotated_image" : image_to_base64(annotated_rgb),
"original_image" : image_to_base64(orig_rgb),
"message" : f"Found {len(detections)} polyp(s)" if detections else "No polyps detected"
})
except Exception as e:
return JSONResponse(
status_code = 500,
content = {
"success" : False,
"error" : str(e),
"message" : "Something went wrong processing the image"
}
)
# ββ Run the server βββββββββββββββββββββββββββββββββββββββββββββββ
if __name__ == "__main__":
uvicorn.run(
"main:app",
host = "0.0.0.0",
port = 8000,
reload = True # auto-restarts when you change code
) |