Spaces:
Sleeping
Sleeping
| 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 | |
| def health_check(): | |
| return {"status": "ok", "model": str(MODEL_PATH)} | |
| 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, | |
| } |