bloombase/plants/services/plantnet.py
Stephan Kerkman 92b682a1bd feat: show Pl@ntNet reference images on confirm screen + save as thumbnail
Extract image_url from Pl@ntNet API response (medium size). Show thumbnail
in each match card on identify_confirm. On plant creation, download the
selected match's reference image and save as PlantPhoto thumbnail. Scan
photos saved as gallery photos (non-thumbnail) with fallback to first scan
photo as thumbnail when Pl@ntNet has no image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 12:51:28 +02:00

70 lines
2.2 KiB
Python

import requests
from django.conf import settings
_API_BASE = 'https://my-api.plantnet.org/v2/identify/all'
_TIMEOUT = 15
class PlantNetError(Exception):
pass
def identify(images):
"""
Call Pl@ntNet identify API.
images: list of (image_bytes, organ) tuples
organ: 'flower' | 'leaf' | 'fruit' | 'bark' | 'habit' | 'auto'
Returns list of match dicts (up to 5), each:
scientific_name, common_names, score, gbif_id, family, genus
Raises PlantNetError on any failure.
"""
if not settings.PLANTNET_API_KEY:
raise PlantNetError('API key not configured.')
files = [
('images', (f'image{i}.jpg', img_bytes, 'image/jpeg'))
for i, (img_bytes, _) in enumerate(images)
]
data = [('organs', organ) for _, organ in images]
try:
resp = requests.post(
_API_BASE,
params={'api-key': settings.PLANTNET_API_KEY, 'lang': 'en'},
files=files,
data=data,
timeout=_TIMEOUT,
)
except requests.Timeout:
raise PlantNetError('Pl@ntNet timed out — try again.')
if resp.status_code == 429:
raise PlantNetError('Daily limit reached (500/day).')
try:
resp.raise_for_status()
except requests.HTTPError:
raise PlantNetError(f'Pl@ntNet API error ({resp.status_code}).')
results = resp.json().get('results', [])
if not results:
raise PlantNetError('No match found — try a different photo or organ.')
try:
return [
{
'scientific_name': r['species']['scientificNameWithoutAuthor'],
'common_names': r['species'].get('commonNames', []),
'score': r['score'],
'gbif_id': (r.get('gbif') or {}).get('id'),
'family': r['species'].get('family', {}).get('scientificNameWithoutAuthor', ''),
'genus': r['species'].get('genus', {}).get('scientificNameWithoutAuthor', ''),
'image_url': ((r.get('images') or [{}])[0].get('url') or {}).get('m'),
}
for r in results[:5]
]
except (KeyError, TypeError) as exc:
raise PlantNetError('Unexpected API response format.') from exc