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
    )