Spaces:
Sleeping
Sleeping
File size: 6,687 Bytes
d11fcd6 9e37f60 e569bb9 26e8165 e569bb9 26e8165 e569bb9 26e8165 e569bb9 26e8165 e569bb9 26e8165 e569bb9 26e8165 e569bb9 26e8165 e569bb9 26e8165 | 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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | from __future__ import annotations
import json
import os
import unicodedata
from io import BytesIO
from pathlib import Path
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
from dotenv import load_dotenv
from fastapi import FastAPI, File, Header, HTTPException, UploadFile, Form
from fastapi.middleware.cors import CORSMiddleware
from PIL import Image
from supabase import Client, create_client
from ultralytics import YOLO
# Pega a pasta atual onde o main.py está
BASE_DIR = Path(__file__).resolve().parent
MODEL_PATH = BASE_DIR / "best.pt"
# Tenta carregar localmente, mas no Hugging Face vai usar as Secrets
load_dotenv()
SUPABASE_URL = os.getenv("SUPABASE_URL") or os.getenv("VITE_SUPABASE_URL")
SUPABASE_KEY = (
os.getenv("SUPABASE_ANON_KEY")
or os.getenv("SUPABASE_PUBLISHABLE_KEY")
or os.getenv("VITE_SUPABASE_PUBLISHABLE_KEY")
or os.getenv("VITE_SUPABASE_ANON_KEY")
)
if not MODEL_PATH.exists():
raise RuntimeError(f"Modelo YOLO nao encontrado em: {MODEL_PATH}")
if not SUPABASE_URL or not SUPABASE_KEY:
raise RuntimeError("Configure SUPABASE_URL/SUPABASE_ANON_KEY ou as variaveis VITE_SUPABASE_*.")
model = YOLO(str(MODEL_PATH))
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
app = FastAPI(title="Visiagro API", description="Deteccao de pragas com YOLOv8")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def _normalize(value: Optional[str]) -> str:
if not value:
return ""
without_accents = "".join(
char for char in unicodedata.normalize("NFD", value) if unicodedata.category(char) != "Mn"
)
return without_accents.lower().replace("_", " ").replace("-", " ").strip()
def _get_user_id(user_response) -> str:
user = getattr(user_response, "user", None)
if user is None and hasattr(user_response, "dict"):
user = user_response.dict().get("user")
if isinstance(user, dict):
user_id = user.get("id")
else:
user_id = getattr(user, "id", None)
if not user_id:
raise HTTPException(status_code=401, detail="Token invalido ou usuario nao encontrado.")
return user_id
def _parse_bearer_token(authorization: Optional[str]) -> str:
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="Envie o token do Supabase no header Authorization.")
return authorization.split(" ", 1)[1].strip()
def _find_peste(label: Optional[str]):
if not label:
return None
response = (
supabase.table("pestes")
.select(
"id,nome_cientifico,nome_comum,descricao_simples,nivel_risco,"
"periodo_mais_comum,acoes_recomendadas,danos_causados"
)
.execute()
)
label_normalized = _normalize(label)
for peste in response.data or []:
candidates = [
peste.get("nome_comum"),
peste.get("nome_cientifico"),
]
if any(_normalize(candidate) == label_normalized for candidate in candidates):
return peste
for peste in response.data or []:
candidates = [
peste.get("nome_comum"),
peste.get("nome_cientifico"),
]
if any(label_normalized in _normalize(candidate) for candidate in candidates):
return peste
return None
def _insert_prediction(token: str, payload: dict):
url = f"{SUPABASE_URL.rstrip('/')}/rest/v1/predictions"
request = Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Prefer": "return=representation",
},
method="POST",
)
try:
with urlopen(request, timeout=20) as response:
body = response.read().decode("utf-8")
return json.loads(body) if body else []
except HTTPError as error:
detail = error.read().decode("utf-8")
raise HTTPException(status_code=error.code, detail=f"Erro ao salvar prediction: {detail}") from error
except URLError as error:
raise HTTPException(status_code=502, detail=f"Falha ao conectar no Supabase: {error.reason}") from error
@app.get("/health")
def health_check():
return {"status": "ok", "model": str(MODEL_PATH)}
@app.post("/analyze", summary="Analisa uma imagem e persiste o resultado")
async def analyze_image(
file: UploadFile = File(...),
authorization: Optional[str] = Header(default=None),
):
token = _parse_bearer_token(authorization)
try:
user_response = supabase.auth.get_user(token)
user_id = _get_user_id(user_response)
except HTTPException:
raise
except Exception as error:
raise HTTPException(status_code=401, detail=f"Falha ao validar usuario: {error}") from error
contents = await file.read()
try:
image = Image.open(BytesIO(contents)).convert("RGB")
except Exception as error:
raise HTTPException(status_code=400, detail="Arquivo enviado nao e uma imagem valida.") from error
results = model.predict(image, verbose=False)
detections = []
for result in results:
for box in result.boxes:
class_id = int(box.cls[0])
label_name = model.names[class_id]
confidence = float(box.conf[0]) if box.conf is not None else None
detections.append(
{
"class_id": class_id,
"label": label_name,
"confidence": confidence,
}
)
top_detection = max(detections, key=lambda item: item["confidence"] or 0, default=None)
unique_labels = list(dict.fromkeys(item["label"] for item in detections))
label_final = ", ".join(unique_labels) if unique_labels else "Nenhuma deteccao"
confidence = top_detection["confidence"] if top_detection else None
peste = _find_peste(top_detection["label"] if top_detection else None)
payload = {
"filename": file.filename,
"label": label_final,
"user_id": user_id,
"peste_id": peste["id"] if peste else None,
"confianca": confidence,
}
inserted = _insert_prediction(token, payload)
return {
"status": "success",
"filename": file.filename,
"label": label_final,
"confianca": confidence,
"peste": peste,
"detections": detections,
"prediction": inserted[0] if inserted else None,
} |