|
|
from fastapi import FastAPI, UploadFile, File, HTTPException, Security, Depends |
|
|
from fastapi.security.api_key import APIKeyHeader |
|
|
from fastapi.responses import JSONResponse, StreamingResponse |
|
|
import uvicorn |
|
|
import io |
|
|
import numpy as np |
|
|
from PIL import Image |
|
|
import cv2 |
|
|
from ultralytics import YOLO |
|
|
import requests |
|
|
import os |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
API_KEY = "1234" |
|
|
api_key_header = APIKeyHeader(name="X-API-Key") |
|
|
|
|
|
def verify_api_key(api_key: str = Security(api_key_header)): |
|
|
if api_key != API_KEY: |
|
|
raise HTTPException(status_code=403, detail="Forbidden") |
|
|
return api_key |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title="Stroke Detection API", |
|
|
version="1.0.0", |
|
|
description=""" |
|
|
🚑 Stroke Detection API using YOLOv8 |
|
|
|
|
|
⚠️ **Disclaimer**: This API is for **research/demo purposes only**. |
|
|
It is **not a certified medical tool**. Do not use for medical decisions. |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
model = YOLO("best.pt") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/v1/predict/") |
|
|
async def predict( |
|
|
file: UploadFile = File(...), |
|
|
api_key: str = Depends(verify_api_key) |
|
|
): |
|
|
try: |
|
|
|
|
|
contents = await file.read() |
|
|
image = Image.open(io.BytesIO(contents)).convert("RGB") |
|
|
np_image = np.array(image) |
|
|
|
|
|
|
|
|
results = model.predict(np_image, conf=0.5, verbose=False) |
|
|
|
|
|
output = [] |
|
|
for r in results: |
|
|
for box in r.boxes: |
|
|
output.append({ |
|
|
"class": r.names[int(box.cls[0].item())], |
|
|
"confidence": float(box.conf[0].item()), |
|
|
"bbox": box.xyxy[0].tolist() |
|
|
}) |
|
|
|
|
|
return JSONResponse(content={"predictions": output}) |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/v1/predict_image/") |
|
|
async def predict_image( |
|
|
file: UploadFile = File(...), |
|
|
api_key: str = Depends(verify_api_key) |
|
|
): |
|
|
try: |
|
|
|
|
|
contents = await file.read() |
|
|
image = Image.open(io.BytesIO(contents)).convert("RGB") |
|
|
np_image = np.array(image) |
|
|
|
|
|
|
|
|
results = model.predict(np_image, conf=0.5, verbose=False) |
|
|
annotated = results[0].plot() |
|
|
|
|
|
|
|
|
annotated_pil = Image.fromarray(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)) |
|
|
img_byte_arr = io.BytesIO() |
|
|
annotated_pil.save(img_byte_arr, format="PNG") |
|
|
img_byte_arr.seek(0) |
|
|
|
|
|
return StreamingResponse(img_byte_arr, media_type="image/png") |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/test_request/") |
|
|
async def test_request(): |
|
|
""" |
|
|
Test interne de l'API déployée sur Hugging Face. |
|
|
Utilise une image locale 'test.jpg' (⚠️ à placer dans ton repo Space). |
|
|
""" |
|
|
try: |
|
|
file_path = "test.jpg" |
|
|
base_url = "https://stroke-ia-api.hf.space" |
|
|
|
|
|
if not os.path.exists(file_path): |
|
|
return {"error": f"{file_path} introuvable dans le Space."} |
|
|
|
|
|
|
|
|
url_predict = f"{base_url}/v1/predict/" |
|
|
files = {"file": open(file_path, "rb")} |
|
|
headers = {"X-API-Key": API_KEY} |
|
|
response = requests.post(url_predict, files=files, headers=headers) |
|
|
json_result = response.json() |
|
|
|
|
|
|
|
|
url_img = f"{base_url}/v1/predict_image/" |
|
|
files = {"file": open(file_path, "rb")} |
|
|
response_img = requests.post(url_img, files=files, headers=headers) |
|
|
|
|
|
with open("result.png", "wb") as f: |
|
|
f.write(response_img.content) |
|
|
|
|
|
return { |
|
|
"message": "✅ Test request exécuté sur Hugging Face API. Résultats sauvegardés.", |
|
|
"json_result": json_result, |
|
|
"saved_image": "result.png" |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
return {"error": str(e)} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
uvicorn.run(app, host="0.0.0.0", port=7860) |
|
|
|