alvaro commited on
Commit
e569bb9
·
1 Parent(s): b232199

reestruturação da pasta api com o modelo para subir no hf

Browse files
Files changed (4) hide show
  1. Dockerfile +7 -0
  2. best.pt +3 -0
  3. main.py +202 -0
  4. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+ WORKDIR /code
3
+ COPY ./requirements.txt /code/requirements.txt
4
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
5
+ RUN apt-get update && apt-get install -y libgl1-mesa-glx
6
+ COPY . .
7
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:62fdbc93cd3ac0ac0225defc690141b97f88be0ad85832e6ac62aeca2b2376d4
3
+ size 22513066
main.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import unicodedata
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+ from urllib.error import HTTPError, URLError
7
+ from urllib.request import Request, urlopen
8
+
9
+ from dotenv import load_dotenv
10
+ from fastapi import FastAPI, File, Header, HTTPException, UploadFile
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from PIL import Image
13
+ from supabase import Client, create_client
14
+ from ultralytics import YOLO
15
+
16
+ # Pega a pasta atual onde o main.py está
17
+ BASE_DIR = Path(__file__).resolve().parent
18
+ MODEL_PATH = BASE_DIR / "best.pt"
19
+
20
+ # Tenta carregar localmente, mas no Hugging Face vai usar as Secrets
21
+ load_dotenv()
22
+
23
+ SUPABASE_URL = os.getenv("SUPABASE_URL") or os.getenv("VITE_SUPABASE_URL")
24
+ SUPABASE_KEY = (
25
+ os.getenv("SUPABASE_ANON_KEY")
26
+ or os.getenv("SUPABASE_PUBLISHABLE_KEY")
27
+ or os.getenv("VITE_SUPABASE_PUBLISHABLE_KEY")
28
+ or os.getenv("VITE_SUPABASE_ANON_KEY")
29
+ )
30
+
31
+ if not MODEL_PATH.exists():
32
+ raise RuntimeError(f"Modelo YOLO nao encontrado em: {MODEL_PATH}")
33
+ if not SUPABASE_URL or not SUPABASE_KEY:
34
+ raise RuntimeError("Configure SUPABASE_URL/SUPABASE_ANON_KEY ou as variaveis VITE_SUPABASE_*.")
35
+
36
+ model = YOLO(str(MODEL_PATH))
37
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
38
+
39
+ app = FastAPI(title="Visiagro API", description="Deteccao de pragas com YOLOv8")
40
+
41
+ app.add_middleware(
42
+ CORSMiddleware, # Faltava esta linha!
43
+ allow_origins=["*"],
44
+ allow_credentials=True,
45
+ allow_methods=["*"],
46
+ allow_headers=["*"],
47
+ )
48
+
49
+
50
+
51
+ def _normalize(value: str | None) -> str:
52
+ if not value:
53
+ return ""
54
+ without_accents = "".join(
55
+ char for char in unicodedata.normalize("NFD", value) if unicodedata.category(char) != "Mn"
56
+ )
57
+ return without_accents.lower().replace("_", " ").replace("-", " ").strip()
58
+
59
+
60
+ def _get_user_id(user_response) -> str:
61
+ user = getattr(user_response, "user", None)
62
+ if user is None and hasattr(user_response, "dict"):
63
+ user = user_response.dict().get("user")
64
+ if isinstance(user, dict):
65
+ user_id = user.get("id")
66
+ else:
67
+ user_id = getattr(user, "id", None)
68
+ if not user_id:
69
+ raise HTTPException(status_code=401, detail="Token invalido ou usuario nao encontrado.")
70
+ return user_id
71
+
72
+
73
+ def _parse_bearer_token(authorization: str | None) -> str:
74
+ if not authorization or not authorization.lower().startswith("bearer "):
75
+ raise HTTPException(status_code=401, detail="Envie o token do Supabase no header Authorization.")
76
+ return authorization.split(" ", 1)[1].strip()
77
+
78
+
79
+ def _find_peste(label: str | None):
80
+ if not label:
81
+ return None
82
+
83
+ response = (
84
+ supabase.table("pestes")
85
+ .select(
86
+ "id,nome_cientifico,nome_comum,descricao_simples,nivel_risco,"
87
+ "periodo_mais_comum,acoes_recomendadas,danos_causados"
88
+ )
89
+ .execute()
90
+ )
91
+ label_normalized = _normalize(label)
92
+
93
+ for peste in response.data or []:
94
+ candidates = [
95
+ peste.get("nome_comum"),
96
+ peste.get("nome_cientifico"),
97
+ ]
98
+ if any(_normalize(candidate) == label_normalized for candidate in candidates):
99
+ return peste
100
+
101
+ for peste in response.data or []:
102
+ candidates = [
103
+ peste.get("nome_comum"),
104
+ peste.get("nome_cientifico"),
105
+ ]
106
+ if any(label_normalized in _normalize(candidate) for candidate in candidates):
107
+ return peste
108
+
109
+ return None
110
+
111
+
112
+ def _insert_prediction(token: str, payload: dict):
113
+ url = f"{SUPABASE_URL.rstrip('/')}/rest/v1/predictions"
114
+ request = Request(
115
+ url,
116
+ data=json.dumps(payload).encode("utf-8"),
117
+ headers={
118
+ "apikey": SUPABASE_KEY,
119
+ "Authorization": f"Bearer {token}",
120
+ "Content-Type": "application/json",
121
+ "Prefer": "return=representation",
122
+ },
123
+ method="POST",
124
+ )
125
+
126
+ try:
127
+ with urlopen(request, timeout=20) as response:
128
+ body = response.read().decode("utf-8")
129
+ return json.loads(body) if body else []
130
+ except HTTPError as error:
131
+ detail = error.read().decode("utf-8")
132
+ raise HTTPException(status_code=error.code, detail=f"Erro ao salvar prediction: {detail}") from error
133
+ except URLError as error:
134
+ raise HTTPException(status_code=502, detail=f"Falha ao conectar no Supabase: {error.reason}") from error
135
+
136
+
137
+ @app.get("/health")
138
+ def health_check():
139
+ return {"status": "ok", "model": str(MODEL_PATH)}
140
+
141
+
142
+ @app.post("/analyze", summary="Analisa uma imagem e persiste o resultado")
143
+ async def analyze_image(
144
+ file: UploadFile = File(...),
145
+ authorization: str | None = Header(default=None),
146
+ ):
147
+ token = _parse_bearer_token(authorization)
148
+ try:
149
+ user_response = supabase.auth.get_user(token)
150
+ user_id = _get_user_id(user_response)
151
+ except HTTPException:
152
+ raise
153
+ except Exception as error:
154
+ raise HTTPException(status_code=401, detail=f"Falha ao validar usuario: {error}") from error
155
+
156
+ contents = await file.read()
157
+ try:
158
+ image = Image.open(BytesIO(contents)).convert("RGB")
159
+ except Exception as error:
160
+ raise HTTPException(status_code=400, detail="Arquivo enviado nao e uma imagem valida.") from error
161
+
162
+ results = model.predict(image, verbose=False)
163
+
164
+ detections = []
165
+ for result in results:
166
+ for box in result.boxes:
167
+ class_id = int(box.cls[0])
168
+ label_name = model.names[class_id]
169
+ confidence = float(box.conf[0]) if box.conf is not None else None
170
+ detections.append(
171
+ {
172
+ "class_id": class_id,
173
+ "label": label_name,
174
+ "confidence": confidence,
175
+ }
176
+ )
177
+
178
+ top_detection = max(detections, key=lambda item: item["confidence"] or 0, default=None)
179
+ unique_labels = list(dict.fromkeys(item["label"] for item in detections))
180
+ label_final = ", ".join(unique_labels) if unique_labels else "Nenhuma deteccao"
181
+ confidence = top_detection["confidence"] if top_detection else None
182
+ peste = _find_peste(top_detection["label"] if top_detection else None)
183
+
184
+ payload = {
185
+ "filename": file.filename,
186
+ "label": label_final,
187
+ "user_id": user_id,
188
+ "peste_id": peste["id"] if peste else None,
189
+ "confianca": confidence,
190
+ }
191
+
192
+ inserted = _insert_prediction(token, payload)
193
+
194
+ return {
195
+ "status": "success",
196
+ "filename": file.filename,
197
+ "label": label_final,
198
+ "confianca": confidence,
199
+ "peste": peste,
200
+ "detections": detections,
201
+ "prediction": inserted[0] if inserted else None,
202
+ }
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ supabase
5
+ pillow
6
+ numpy
7
+ ultralytics
8
+ python-dotenv