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>
70 lines
2.2 KiB
Python
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
|