feat(backend): services PlantNet et YOLO pour identification de plantes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
BACKEND_PORT=8000
|
BACKEND_PORT=8060
|
||||||
CORS_ORIGINS=http://localhost:5173,http://localhost
|
CORS_ORIGINS=http://localhost:5173,http://localhost:8061
|
||||||
DATABASE_URL=sqlite:////data/jardin.db
|
DATABASE_URL=sqlite:////data/jardin.db
|
||||||
UPLOAD_DIR=/data/uploads
|
UPLOAD_DIR=/data/uploads
|
||||||
VITE_API_URL=http://localhost:8000
|
VITE_API_URL=http://localhost:8060
|
||||||
|
PLANTNET_API_KEY=2b1088cHCJ4c7Cn2Vqq67xfve
|
||||||
|
AI_SERVICE_URL=http://ai-service:8070
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
|||||||
35
backend/app/services/plantnet.py
Normal file
35
backend/app/services/plantnet.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
PLANTNET_KEY = os.environ.get("PLANTNET_API_KEY", "2b1088cHCJ4c7Cn2Vqq67xfve")
|
||||||
|
PLANTNET_URL = "https://my-api.plantnet.org/v2/identify/all"
|
||||||
|
|
||||||
|
|
||||||
|
async def identify(image_bytes: bytes, filename: str = "photo.jpg") -> List[dict]:
|
||||||
|
"""Appelle PlantNet et retourne les 3 meilleures identifications."""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
PLANTNET_URL,
|
||||||
|
params={"api-key": PLANTNET_KEY, "nb-results": 3},
|
||||||
|
files={"images": (filename, image_bytes, "image/jpeg")},
|
||||||
|
data={"organs": ["auto"]},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for r in data.get("results", [])[:3]:
|
||||||
|
species = r.get("species", {})
|
||||||
|
common_names = species.get("commonNames", [])
|
||||||
|
results.append({
|
||||||
|
"species": species.get("scientificNameWithoutAuthor", ""),
|
||||||
|
"common_name": common_names[0] if common_names else "",
|
||||||
|
"confidence": round(r.get("score", 0.0), 3),
|
||||||
|
"image_url": (r.get("images", [{}]) or [{}])[0].get("url", {}).get("m", ""),
|
||||||
|
})
|
||||||
|
return results
|
||||||
45
backend/app/services/yolo_service.py
Normal file
45
backend/app/services/yolo_service.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
AI_SERVICE_URL = os.environ.get("AI_SERVICE_URL", "http://localhost:8070")
|
||||||
|
|
||||||
|
# Mapping class_name YOLO → nom commun français (partiel)
|
||||||
|
_NOMS_FR = {
|
||||||
|
"Tomato___healthy": "Tomate (saine)",
|
||||||
|
"Tomato___Early_blight": "Tomate (mildiou précoce)",
|
||||||
|
"Tomato___Late_blight": "Tomate (mildiou tardif)",
|
||||||
|
"Pepper__bell___healthy": "Poivron (sain)",
|
||||||
|
"Apple___healthy": "Pommier (sain)",
|
||||||
|
"Potato___healthy": "Pomme de terre (saine)",
|
||||||
|
"Grape___healthy": "Vigne (saine)",
|
||||||
|
"Corn_(maize)___healthy": "Maïs (sain)",
|
||||||
|
"Strawberry___healthy": "Fraisier (sain)",
|
||||||
|
"Peach___healthy": "Pêcher (sain)",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def identify(image_bytes: bytes) -> List[dict]:
|
||||||
|
"""Appelle l'ai-service interne et retourne les détections YOLO."""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{AI_SERVICE_URL}/detect",
|
||||||
|
files={"file": ("photo.jpg", image_bytes, "image/jpeg")},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for det in data[:3]:
|
||||||
|
cls = det.get("class_name", "")
|
||||||
|
results.append({
|
||||||
|
"species": cls.replace("___", " — ").replace("_", " "),
|
||||||
|
"common_name": _NOMS_FR.get(cls, cls.split("___")[0].replace("_", " ")),
|
||||||
|
"confidence": det.get("confidence", 0.0),
|
||||||
|
"image_url": "",
|
||||||
|
})
|
||||||
|
return results
|
||||||
Reference in New Issue
Block a user