Best Practices¶
Guidelines for getting the most accurate diagnoses and building robust integrations.
Image Quality¶
Do's¶
- Use natural lighting - Outdoor daylight provides the best color accuracy
- Focus on symptoms - Get close-ups of affected areas
- Include context - Show both healthy and affected parts
- Keep steady - Avoid motion blur
- Multiple angles - When possible, capture different views
Don'ts¶
- Avoid flash - Can wash out colors and create shadows
- Avoid backlighting - Don't shoot against the sun
- Avoid extreme zoom - Very zoomed images lose context
- Avoid filters - Don't apply Instagram-style filters
- Avoid screenshots - Upload original photos, not screenshots
Example: Good vs Bad Images¶
| Good | Bad |
|---|---|
| Clear, focused close-up of leaf | Blurry, out of focus |
| Natural daylight | Dark, shadowy |
| Shows affected area clearly | Symptom too small in frame |
| Original photo | Screenshot of photo |
Provide Context¶
The more context you provide, the more accurate the diagnosis:
Always Include (When Known)¶
| Parameter | Impact on Accuracy |
|---|---|
crop_type |
High - Diseases are crop-specific |
region |
High - Some diseases are region-specific |
growth_stage |
Medium - Symptoms vary by plant maturity |
description |
Medium - Your observations help AI focus |
Example: Context Matters¶
# Basic request - less accurate
result = diagnose(image)
# With context - more accurate
result = diagnose(
image,
crop_type="tomato",
region="Kenya",
growth_stage="flowering",
description="Yellow spots appeared after heavy rain last week"
)
Choose the Right Detail Level¶
| Level | Best For | What You Get |
|---|---|---|
simple |
Farmers, field workers | Plain language, key actions only |
standard |
Extension officers, technicians | Balanced technical info |
expert |
Agronomists, researchers | Full scientific detail |
Example Outputs¶
Simple:
Your tomato plant has a fungus problem. Remove the bad leaves and spray with copper-based fungicide every week.
Standard:
Early Blight (Alternaria solani) detected with 87% confidence. The disease affects lower leaves first. Treat with chlorothalonil or copper hydroxide, applying every 7-10 days.
Expert:
Alternaria solani infection confirmed. Pathogen thrives at 24-29°C with >90% RH. Recommended treatment: Chlorothalonil (1.5kg/ha) or Mancozeb (2kg/ha) at 7-10 day intervals. Consider resistance management with fungicide rotation.
Handle Responses Properly¶
Always Check These Fields¶
result = diagnose(image)
# 1. Is it a plant?
if not result['is_plant']:
print("Please upload a photo of a plant")
return
# 2. Any image quality issues?
if result['image_quality_issue']:
print(f"Image problem: {result['image_quality_issue']}")
return
# 3. Could health be determined?
if result['crop_health'] == 'unknown':
print("Could not determine plant health")
return
# 4. Any diagnoses found?
if not result['diagnoses']:
print("No diseases detected - plant appears healthy")
return
# 5. Process diagnoses by confidence
for diagnosis in result['diagnoses']:
if diagnosis['confidence'] >= 0.7:
print(f"Likely: {diagnosis['name']}")
elif diagnosis['confidence'] >= 0.4:
print(f"Possible: {diagnosis['name']}")
Confidence Score Interpretation¶
| Score | Interpretation | Action |
|---|---|---|
| 0.8+ | High confidence | Proceed with treatment |
| 0.5-0.8 | Moderate | Consider treatment, monitor |
| 0.3-0.5 | Low | Monitor, get second opinion |
| <0.3 | Very low | Likely not this disease |
Error Handling¶
Implement Retry Logic¶
import time
from functools import wraps
def with_retry(max_attempts=3, backoff_factor=2):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_attempts - 1:
wait = backoff_factor ** attempt
time.sleep(wait)
raise last_error
return wrapper
return decorator
@with_retry(max_attempts=3)
def diagnose_plant(image_path):
# Your diagnosis code
pass
Handle Specific Errors¶
def diagnose_safely(image_path, **kwargs):
try:
result = client.diagnose(image_path, **kwargs)
if result.get('error'):
# Analysis-level error
return handle_analysis_error(result['error'])
return result
except requests.exceptions.Timeout:
return {"error": "Request timed out. Please try again."}
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
return {"error": "Rate limit exceeded. Please wait."}
elif e.response.status_code == 400:
return {"error": f"Invalid request: {e.response.json().get('detail')}"}
else:
return {"error": f"Server error: {e.response.status_code}"}
except Exception as e:
return {"error": f"Unexpected error: {str(e)}"}
Performance Optimization¶
1. Compress Images Client-Side¶
Large images increase upload time without improving accuracy:
from PIL import Image
import io
def optimize_image(image_path, max_dimension=1920, quality=85):
"""Optimize image for API upload."""
img = Image.open(image_path)
# Resize if too large
if max(img.size) > max_dimension:
ratio = max_dimension / max(img.size)
new_size = tuple(int(dim * ratio) for dim in img.size)
img = img.resize(new_size, Image.LANCZOS)
# Convert to RGB if needed
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# Save to buffer
buffer = io.BytesIO()
img.save(buffer, format='JPEG', quality=quality, optimize=True)
buffer.seek(0)
return buffer
2. Use Connection Pooling¶
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Create session with retry logic
session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries))
# Reuse session for all requests
def diagnose(image_path):
return session.post(
'https://api.tajirifarm.com/diagnosis/',
files={'image': open(image_path, 'rb')}
).json()
3. Cache Results¶
from functools import lru_cache
import hashlib
def get_image_hash(image_path):
with open(image_path, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
@lru_cache(maxsize=100)
def diagnose_cached(image_hash, crop_type=None, region=None):
# This caches results for identical images
return client.diagnose(image_hash, crop_type=crop_type, region=region)
Security¶
Validate User Uploads¶
import magic # python-magic library
ALLOWED_MIMES = {'image/jpeg', 'image/png', 'image/webp'}
MAX_SIZE = 10 * 1024 * 1024 # 10MB
def validate_upload(file_path):
"""Validate uploaded file before sending to API."""
# Check file size
import os
if os.path.getsize(file_path) > MAX_SIZE:
raise ValueError("File too large (max 10MB)")
# Check actual MIME type (not just extension)
mime = magic.from_file(file_path, mime=True)
if mime not in ALLOWED_MIMES:
raise ValueError(f"Invalid file type: {mime}")
return True
Don't Log Sensitive Data¶
import logging
# Good - log request metadata
logging.info(f"Diagnosis request: crop={crop_type}, region={region}")
# Bad - don't log full image data
# logging.info(f"Image data: {image_bytes}")
Testing¶
Use Health Check¶
def check_api_availability():
"""Check if API is available before processing."""
try:
response = requests.get(
'https://api.tajirifarm.com/health',
timeout=5
)
return response.json().get('status') == 'healthy'
except:
return False
# Before batch processing
if not check_api_availability():
print("API is unavailable. Please try later.")
exit(1)
Save Request IDs¶
def diagnose_with_logging(image_path, **kwargs):
"""Diagnose and log request ID for debugging."""
response = requests.post(
'https://api.tajirifarm.com/diagnosis/',
files={'image': open(image_path, 'rb')},
data=kwargs
)
request_id = response.headers.get('X-Request-ID')
logging.info(f"Request ID: {request_id}")
result = response.json()
result['_request_id'] = request_id # Store for later reference
return result