|
|
from fastapi import FastAPI, UploadFile, File |
|
|
from fastapi.responses import JSONResponse, StreamingResponse |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from ultralytics import YOLO |
|
|
import numpy as np |
|
|
from PIL import Image |
|
|
import io |
|
|
import cv2 |
|
|
import requests |
|
|
|
|
|
|
|
|
model = YOLO("best.pt") |
|
|
|
|
|
|
|
|
CLASS_NAMES = [ |
|
|
"normalEye", |
|
|
"normalMouth", |
|
|
"strokeEyeMid", |
|
|
"strokeEyeSevere", |
|
|
"strokeEyeWeak", |
|
|
"strokeMouthMid", |
|
|
"strokeMouthSevere", |
|
|
"strokeMouthWeak" |
|
|
] |
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title="Stroke-IA Detection API", |
|
|
description="REST API for stroke sign detection (tech demo, not medical advice).", |
|
|
version="1.0" |
|
|
) |
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
@app.get("/") |
|
|
async def root(): |
|
|
return {"message": "Stroke-IA API is running. Use /predict/ or /predict_image/."} |
|
|
|
|
|
@app.post("/predict/") |
|
|
async def predict(file: UploadFile = File(...)): |
|
|
try: |
|
|
contents = await file.read() |
|
|
image = Image.open(io.BytesIO(contents)).convert("RGB") |
|
|
np_image = np.array(image) |
|
|
|
|
|
results = model.predict(source=np_image, conf=0.85, verbose=False) |
|
|
|
|
|
if len(results[0].boxes) == 0: |
|
|
return { |
|
|
"message": "✅ No stroke signs detected (confidence ≥ 85%)", |
|
|
"detections": [], |
|
|
"summary": "Healthy face detected with no significant asymmetry." |
|
|
} |
|
|
|
|
|
detections = [] |
|
|
for box, score, cls in zip(results[0].boxes.xyxy.tolist(), |
|
|
results[0].boxes.conf.tolist(), |
|
|
results[0].boxes.cls.tolist()): |
|
|
label = CLASS_NAMES[int(cls)] |
|
|
detections.append({ |
|
|
"box": box, |
|
|
"score": float(score), |
|
|
"class": int(cls), |
|
|
"label": label |
|
|
}) |
|
|
|
|
|
best_det = max(detections, key=lambda x: x["score"]) |
|
|
summary = f"⚠️ {best_det['label']} detected with {best_det['score']*100:.1f}% confidence." |
|
|
|
|
|
return { |
|
|
"message": "⚠️ Possible stroke signs detected", |
|
|
"detections": detections, |
|
|
"summary": summary |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
return JSONResponse({"error": str(e)}, status_code=500) |
|
|
|
|
|
@app.post("/predict_image/") |
|
|
async def predict_image(file: UploadFile = File(...)): |
|
|
try: |
|
|
contents = await file.read() |
|
|
image = Image.open(io.BytesIO(contents)).convert("RGB") |
|
|
np_image = np.array(image) |
|
|
|
|
|
results = model.predict(source=np_image, conf=0.85, 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: |
|
|
return JSONResponse({"error": str(e)}, status_code=500) |
|
|
|
|
|
|
|
|
@app.get("/test_request/") |
|
|
async def test_request(): |
|
|
""" |
|
|
Teste l'API déployée sur Hugging Face : envoie une image vers /predict et /predict_image, |
|
|
puis sauvegarde les résultats. |
|
|
""" |
|
|
try: |
|
|
file_path = "test.jpg" |
|
|
base_url = "https://Stroke-ia.hf.space" |
|
|
|
|
|
|
|
|
url_predict = f"{base_url}/predict/" |
|
|
files = {"file": open(file_path, "rb")} |
|
|
response = requests.post(url_predict, files=files) |
|
|
json_result = response.json() |
|
|
|
|
|
|
|
|
url_img = f"{base_url}/predict_image/" |
|
|
files = {"file": open(file_path, "rb")} |
|
|
response_img = requests.post(url_img, files=files) |
|
|
|
|
|
with open("result.png", "wb") as f: |
|
|
f.write(response_img.content) |
|
|
|
|
|
return { |
|
|
"message": "✅ Test request executed on Hugging Face API. Results saved.", |
|
|
"json_result": json_result, |
|
|
"saved_image": "result.png" |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
return {"error": str(e)} |
|
|
|