| 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
|
|
|
|
|
| app = FastAPI(
|
| title = "Polyp Detection API",
|
| description = "YOLOv8 polyp detection and segmentation",
|
| version = "1.0.0"
|
| )
|
|
|
|
|
|
|
|
|
| app.add_middleware(
|
| CORSMiddleware,
|
| allow_origins = ["*"],
|
| allow_credentials = True,
|
| allow_methods = ["*"],
|
| allow_headers = ["*"],
|
| )
|
|
|
|
|
|
|
|
|
| MODEL_PATH = "polyp_best_model.pt"
|
|
|
| print(f"Loading model from {MODEL_PATH}...")
|
| model = YOLO(MODEL_PATH)
|
| print("β
Model loaded successfully!")
|
|
|
|
|
|
|
|
|
|
|
| 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}"
|
|
|
|
|
|
|
|
|
| @app.get("/")
|
| def health_check():
|
| return {
|
| "status" : "online",
|
| "message" : "Polyp Detection API is running"
|
| }
|
|
|
|
|
|
|
|
|
| @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:
|
|
|
| contents = await file.read()
|
| pil_image = Image.open(io.BytesIO(contents)).convert("RGB")
|
| img_array = np.array(pil_image)
|
|
|
|
|
| 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)
|
|
|
|
|
| results = model(
|
| tmp_path,
|
| conf = 0.15,
|
| iou = 0.4,
|
| verbose = False
|
| )
|
| os.remove(tmp_path)
|
|
|
|
|
| annotated = results[0].plot(conf=True, masks=True, boxes=True)
|
| annotated_rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
|
|
|
|
|
| frame_height, frame_width = img_resized.shape[:2]
|
| total_frame_pixels = frame_height * frame_width
|
| cancer_pixels = 0
|
|
|
|
|
| if results[0].masks is not None:
|
| for mask in results[0].masks.data:
|
|
|
| mask_np = mask.cpu().numpy()
|
| cancer_pixels += int(np.sum(mask_np))
|
|
|
| cancer_spread_percentage = (cancer_pixels / total_frame_pixels) * 100 if total_frame_pixels > 0 else 0
|
|
|
|
|
| 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),
|
| "bbox" : {
|
| "x1" : round(xyxy[0]),
|
| "y1" : round(xyxy[1]),
|
| "x2" : round(xyxy[2]),
|
| "y2" : round(xyxy[3])
|
| }
|
| })
|
|
|
|
|
| orig_resized = cv2.resize(
|
| cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR),
|
| (640, 640)
|
| )
|
| orig_rgb = cv2.cvtColor(orig_resized, cv2.COLOR_BGR2RGB)
|
|
|
|
|
| return JSONResponse(content={
|
| "success" : True,
|
| "polyp_count" : len(detections),
|
| "cancer_spread_percentage" : round(cancer_spread_percentage, 2),
|
| "total_cancer_pixels" : cancer_pixels,
|
| "total_frame_pixels" : total_frame_pixels,
|
| "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"
|
| }
|
| )
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
| uvicorn.run(
|
| "main:app",
|
| host = "0.0.0.0",
|
| port = 8000,
|
| reload = True
|
| ) |