bharath1108 commited on
Commit
1c36f7b
·
verified ·
1 Parent(s): 77da6c6

Upload 5 files

Browse files
Files changed (5) hide show
  1. .env +0 -0
  2. .gitattributes +0 -34
  3. .gitignore +36 -0
  4. app.py +556 -0
  5. requirements.txt +20 -0
.env ADDED
File without changes
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.pt filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ env/
7
+ venv/
8
+ ENV/
9
+ *.egg-info/
10
+ dist/
11
+ build/
12
+
13
+ # Flask
14
+ instance/
15
+ .webassets-cache
16
+
17
+ # Environment
18
+ .env
19
+ .env.local
20
+
21
+ # Uploads (but keep the directory structure)
22
+ static/uploads/*
23
+ !static/uploads/.gitkeep
24
+
25
+ # IDE
26
+ .vscode/
27
+ .idea/
28
+ *.swp
29
+ *.swo
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
34
+
35
+ # Logs
36
+ *.log
app.py ADDED
@@ -0,0 +1,556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, session, redirect
2
+ import os
3
+ from werkzeug.utils import secure_filename
4
+ import json
5
+ from PIL import Image
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ import torchvision.transforms as transforms
10
+ import requests
11
+ from dotenv import load_dotenv
12
+ from datetime import datetime
13
+ import logging
14
+ from pathlib import Path
15
+
16
+ # Configure logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Load environment variables
21
+ load_dotenv()
22
+
23
+ app = Flask(__name__)
24
+ app.secret_key = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')
25
+ app.config['UPLOAD_FOLDER'] = 'static/uploads/'
26
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max
27
+ app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'webp'}
28
+
29
+ # Model configuration
30
+ MODEL_DIR = 'model'
31
+ MODEL_FILENAME = 'plant_disease_model_1_latest.pt'
32
+ MODEL_PATH = os.path.join(MODEL_DIR, MODEL_FILENAME)
33
+ MODEL_URL = "https://huggingface.co/spaces/bharath1108/crop-disease-model/resolve/main/plant_disease_model_1_latest.pt"
34
+
35
+ # Ensure necessary directories exist
36
+ try:
37
+ os.makedirs(MODEL_DIR, exist_ok=True)
38
+ if not os.path.exists(app.config['UPLOAD_FOLDER']):
39
+ os.makedirs(app.config['UPLOAD_FOLDER'])
40
+ elif not os.path.isdir(app.config['UPLOAD_FOLDER']):
41
+ os.remove(app.config['UPLOAD_FOLDER'])
42
+ os.makedirs(app.config['UPLOAD_FOLDER'])
43
+ except Exception as e:
44
+ logger.warning(f"Could not create directories: {e}. Will attempt to use them anyway.")
45
+
46
+ # OpenRouter API configuration
47
+ OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
48
+ OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
49
+ APP_URL = os.environ.get("APP_URL", "http://localhost:5000")
50
+
51
+ # Global variables for model and translations
52
+ model = None
53
+ device = None
54
+ transforms_pipeline = None
55
+ translations = None
56
+ class_labels = None
57
+ disease_info = None
58
+
59
+ def download_model_from_huggingface():
60
+ """Download PyTorch model from Hugging Face if not exists locally"""
61
+ if os.path.exists(MODEL_PATH):
62
+ file_size = os.path.getsize(MODEL_PATH)
63
+ logger.info(f"Model already exists at {MODEL_PATH} ({file_size / (1024*1024):.2f} MB)")
64
+ return True
65
+
66
+ logger.info(f"Downloading model from Hugging Face: {MODEL_URL}")
67
+ try:
68
+ # Download with streaming to handle large files
69
+ response = requests.get(MODEL_URL, stream=True, timeout=300)
70
+ response.raise_for_status()
71
+
72
+ # Get total file size
73
+ total_size = int(response.headers.get('content-length', 0))
74
+ logger.info(f"Model size: {total_size / (1024*1024):.2f} MB")
75
+
76
+ # Download in chunks with progress
77
+ downloaded_size = 0
78
+ chunk_size = 8192
79
+
80
+ with open(MODEL_PATH, 'wb') as f:
81
+ for chunk in response.iter_content(chunk_size=chunk_size):
82
+ if chunk:
83
+ f.write(chunk)
84
+ downloaded_size += len(chunk)
85
+ if total_size > 0:
86
+ progress = (downloaded_size / total_size) * 100
87
+ if downloaded_size % (1024 * 1024 * 10) == 0: # Log every 10MB
88
+ logger.info(f"Download progress: {progress:.1f}%")
89
+
90
+ logger.info(f"Model downloaded successfully to {MODEL_PATH}")
91
+ return True
92
+
93
+ except requests.exceptions.RequestException as e:
94
+ logger.error(f"Error downloading model from Hugging Face: {str(e)}")
95
+ if os.path.exists(MODEL_PATH):
96
+ os.remove(MODEL_PATH)
97
+ return False
98
+ except Exception as e:
99
+ logger.error(f"Unexpected error during model download: {str(e)}")
100
+ if os.path.exists(MODEL_PATH):
101
+ os.remove(MODEL_PATH)
102
+ return False
103
+
104
+ def load_resources():
105
+ """Load PyTorch model, translations, and class labels"""
106
+ global model, device, transforms_pipeline, translations, class_labels, disease_info
107
+
108
+ try:
109
+ # Set device - force CPU to save memory
110
+ device = torch.device('cpu')
111
+ logger.info(f"Using device: {device}")
112
+
113
+ # Download model if needed
114
+ model_downloaded = download_model_from_huggingface()
115
+
116
+ # Load PyTorch model with memory optimization
117
+ if model_downloaded and os.path.exists(MODEL_PATH):
118
+ try:
119
+ logger.info(f"Loading model from {MODEL_PATH}...")
120
+
121
+ # Memory optimization: Load with reduced precision
122
+ import gc
123
+ gc.collect() # Force garbage collection before loading
124
+
125
+ # Load the entire model (weights + architecture)
126
+ model = torch.load(
127
+ MODEL_PATH,
128
+ map_location=device,
129
+ weights_only=False
130
+ )
131
+ model.eval()
132
+
133
+ # Use half precision to reduce memory (if supported)
134
+ try:
135
+ model = model.half()
136
+ logger.info("Model converted to half precision (FP16)")
137
+ except:
138
+ logger.info("Half precision not supported, using full precision")
139
+
140
+ # Force cleanup
141
+ gc.collect()
142
+
143
+ # Verify model loaded correctly
144
+ if hasattr(model, '__class__'):
145
+ logger.info(f"PyTorch model loaded successfully: {model.__class__.__name__}")
146
+ else:
147
+ logger.warning("Model loaded but structure unclear")
148
+
149
+ except Exception as e:
150
+ logger.error(f"Error loading PyTorch model: {str(e)}")
151
+ logger.warning("Falling back to mock predictions")
152
+ model = None
153
+ else:
154
+ logger.warning(f"Model not found at {MODEL_PATH}. Using mock predictions.")
155
+
156
+ # Define image transforms (standard ImageNet normalization)
157
+ transforms_pipeline = transforms.Compose([
158
+ transforms.Resize((224, 224)),
159
+ transforms.ToTensor(),
160
+ transforms.Normalize(mean=[0.485, 0.456, 0.406],
161
+ std=[0.229, 0.224, 0.225])
162
+ ])
163
+
164
+ # Load translations
165
+ with open('translations/crop_names.json', 'r', encoding='utf-8') as f:
166
+ translations = json.load(f)
167
+ logger.info("Translations loaded successfully")
168
+
169
+ # Load class labels
170
+ with open('model/class_labels.json', 'r', encoding='utf-8') as f:
171
+ class_labels = json.load(f)
172
+ logger.info(f"Class labels loaded: {len(class_labels)} classes")
173
+
174
+ # Load disease information
175
+ try:
176
+ with open('translations/disease_info.json', 'r', encoding='utf-8') as f:
177
+ disease_info = json.load(f)
178
+ logger.info("Disease information loaded successfully")
179
+ except FileNotFoundError:
180
+ logger.warning("disease_info.json not found. Using basic info.")
181
+ disease_info = {}
182
+
183
+ except Exception as e:
184
+ logger.error(f"Error loading resources: {str(e)}")
185
+ # Initialize with empty data for development
186
+ translations = {"crops": {}, "diseases": {}, "ui_elements": {}}
187
+ class_labels = {}
188
+ disease_info = {}
189
+
190
+ def allowed_file(filename):
191
+ """Check if file extension is allowed"""
192
+ return '.' in filename and \
193
+ filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
194
+
195
+ def get_translation(key, lang='english'):
196
+ """Get translation for a given key and language"""
197
+ if not translations:
198
+ return key
199
+
200
+ parts = key.split('.')
201
+ current = translations
202
+
203
+ try:
204
+ for part in parts:
205
+ current = current[part]
206
+ return current.get(lang, current.get('english', key))
207
+ except (KeyError, TypeError):
208
+ return key
209
+
210
+ def preprocess_image(image_path):
211
+ """Preprocess image for PyTorch model prediction"""
212
+ try:
213
+ img = Image.open(image_path).convert('RGB')
214
+ img_tensor = transforms_pipeline(img)
215
+ img_tensor = img_tensor.unsqueeze(0) # Add batch dimension
216
+
217
+ # Convert to half precision if model uses it
218
+ if model is not None:
219
+ try:
220
+ img_tensor = img_tensor.half()
221
+ except:
222
+ pass
223
+
224
+ return img_tensor
225
+ except Exception as e:
226
+ logger.error(f"Error preprocessing image: {str(e)}")
227
+ raise
228
+
229
+ def predict_disease(image_path):
230
+ """Predict disease from leaf image using PyTorch model"""
231
+ try:
232
+ if model is None:
233
+ # Mock prediction for development/testing
234
+ logger.warning("Using mock prediction - model not loaded")
235
+ return {
236
+ 'crop': 'tomato',
237
+ 'disease': 'early_blight',
238
+ 'confidence': 89.5,
239
+ 'is_healthy': False,
240
+ 'severity': 'moderate'
241
+ }
242
+
243
+ # Preprocess image
244
+ img_tensor = preprocess_image(image_path).to(device)
245
+
246
+ # Predict
247
+ with torch.no_grad():
248
+ outputs = model(img_tensor)
249
+ probabilities = torch.nn.functional.softmax(outputs, dim=1)
250
+ confidence, predicted = torch.max(probabilities, 1)
251
+
252
+ class_idx = predicted.item()
253
+ confidence_score = confidence.item() * 100
254
+
255
+ logger.info(f"Prediction: class {class_idx}, confidence {confidence_score:.2f}%")
256
+
257
+ # Get disease info from class labels
258
+ disease_info_item = class_labels.get(str(class_idx), {
259
+ 'crop': 'unknown',
260
+ 'disease': 'unknown',
261
+ 'is_healthy': False
262
+ })
263
+
264
+ # Determine severity based on confidence and disease type
265
+ severity = 'mild'
266
+ if not disease_info_item.get('is_healthy', False):
267
+ if confidence_score > 85:
268
+ severity = 'severe'
269
+ elif confidence_score > 70:
270
+ severity = 'moderate'
271
+
272
+ return {
273
+ 'crop': disease_info_item.get('crop', 'unknown'),
274
+ 'disease': disease_info_item.get('disease', 'unknown'),
275
+ 'confidence': round(confidence_score, 2),
276
+ 'is_healthy': disease_info_item.get('is_healthy', False),
277
+ 'severity': severity
278
+ }
279
+
280
+ except Exception as e:
281
+ logger.error(f"Error predicting disease: {str(e)}")
282
+ raise
283
+
284
+ def get_disease_info(crop_name, disease_name, language='english'):
285
+ """Get detailed disease information"""
286
+ if not disease_info:
287
+ return None
288
+
289
+ key = f"{crop_name}_{disease_name}"
290
+ info = disease_info.get(key, {})
291
+
292
+ if info:
293
+ return info.get(language, info.get('english', None))
294
+ return None
295
+
296
+ def generate_treatment_advice(crop_name, disease_name, language, location=""):
297
+ """Generate treatment recommendations using OpenRouter LLaMA"""
298
+
299
+ # Language-specific instructions
300
+ language_instructions = {
301
+ 'english': "Provide treatment advice in English.",
302
+ 'telugu': "Provide treatment advice in Telugu (తెలుగు). Use Telugu script and simple farming terminology.",
303
+ 'hindi': "Provide treatment advice in Hindi (हिंदी). Use Devanagari script and simple farming terminology."
304
+ }
305
+
306
+ # Get translated crop and disease names
307
+ crop_display = get_translation(f'crops.{crop_name}', language)
308
+ disease_display = get_translation(f'diseases.{disease_name}', language)
309
+
310
+ # Get additional disease info if available
311
+ extra_info = get_disease_info(crop_name, disease_name, language)
312
+ extra_context = f"\n**Additional Context**: {extra_info}" if extra_info else ""
313
+
314
+ prompt = f"""{language_instructions.get(language, language_instructions['english'])}
315
+
316
+ **Crop**: {crop_display} ({crop_name})
317
+ **Disease Detected**: {disease_display} ({disease_name})
318
+ **Farmer Location**: {location or 'India'}{extra_context}
319
+
320
+ As an agricultural expert, provide clear, actionable treatment advice for this crop disease. Structure your response as follows:
321
+
322
+ 1. **रोग की जानकारी / Disease Overview** (2-3 sentences)
323
+ 2. **तुरंत करें / Immediate Action** (What to do right now)
324
+ 3. **जैविक उपचार / Organic Treatment** (3-4 natural methods with dosage)
325
+ 4. **रासायनिक उपचार / Chemical Treatment** (2-3 pesticides/fungicides with dosage)
326
+ 5. **रोकथाम / Prevention** (How to prevent in future)
327
+ 6. **ठीक होने का समय / Recovery Time**
328
+
329
+ Keep language simple and practical. Use measurements familiar to Indian farmers (liters per acre, grams per liter). Focus on treatments available in rural India."""
330
+
331
+ try:
332
+ # Make request to OpenRouter API using requests
333
+ response = requests.post(
334
+ url=OPENROUTER_URL,
335
+ headers={
336
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
337
+ "Content-Type": "application/json",
338
+ "HTTP-Referer": APP_URL,
339
+ "X-Title": "Crop Disease Detector"
340
+ },
341
+ json={
342
+ "model": "meta-llama/llama-3.1-8b-instruct:free",
343
+ "messages": [
344
+ {
345
+ "role": "system",
346
+ "content": "You are an expert agricultural advisor helping Indian farmers. You speak Telugu, Hindi, and English fluently. Provide practical, field-tested advice suitable for small and medium farmers."
347
+ },
348
+ {
349
+ "role": "user",
350
+ "content": prompt
351
+ }
352
+ ],
353
+ "temperature": 0.7,
354
+ "max_tokens": 1500
355
+ },
356
+ timeout=30 # 30 second timeout
357
+ )
358
+
359
+ response.raise_for_status() # Raise exception for bad status codes
360
+
361
+ result = response.json()
362
+ return result['choices'][0]['message']['content']
363
+
364
+ except requests.exceptions.Timeout:
365
+ logger.error("OpenRouter API request timed out")
366
+ fallback_messages = {
367
+ 'english': f"Treatment advice request timed out. Please try again in a moment.",
368
+ 'hindi': f"उपचार सलाह अनुरोध समय समाप्त हो गया। कृपया कुछ देर में फिर से प्रयास करें।",
369
+ 'telugu': f"చికిత్స సలహా అభ్యర్థన సమయం ముగిసింది. దయచేసి కొద్దిసేపు తర్వాత మళ్లీ ప్రయత్నించండి."
370
+ }
371
+ return fallback_messages.get(language, fallback_messages['english'])
372
+
373
+ except requests.exceptions.RequestException as e:
374
+ logger.error(f"Error calling OpenRouter API: {str(e)}")
375
+ # Fallback response
376
+ fallback_messages = {
377
+ 'english': f"Treatment advice for {disease_display} in {crop_display} is being prepared. Please try again in a moment.",
378
+ 'hindi': f"{crop_display} में {disease_display} के लिए उपचार सलाह तैयार की जा रही है। कृपया कुछ देर में फिर से प्रयास करें।",
379
+ 'telugu': f"{crop_display}లో {disease_display} కోసం చికిత్స సలహా సిద్ధం చేయబడుతోంది. దయచేసి కొద్దిసేపు తర్వాత మళ్లీ ప్రయత్నించండి."
380
+ }
381
+ return fallback_messages.get(language, fallback_messages['english'])
382
+
383
+ except Exception as e:
384
+ logger.error(f"Unexpected error generating treatment advice: {str(e)}")
385
+ fallback_messages = {
386
+ 'english': f"Unable to generate treatment advice at this time. Please try again later.",
387
+ 'hindi': f"इस समय उपचार सलाह तैयार करने में असमर्थ। कृपया बाद में पुन: प्रयास करें।",
388
+ 'telugu': f"ఈ సమయంలో చికిత్స సలహాను రూపొందించడం సాధ్యం కాలేదు. దయచేసి తర్వాత మళ్లీ ప్రయత్నించండి."
389
+ }
390
+ return fallback_messages.get(language, fallback_messages['english'])
391
+
392
+ @app.route('/')
393
+ def index():
394
+ """Home page with language selection"""
395
+ language = session.get('language', 'english')
396
+ return render_template('index.html',
397
+ lang=language,
398
+ translations=translations)
399
+
400
+ @app.route('/set-language/<lang>')
401
+ def set_language(lang):
402
+ """Set user's preferred language"""
403
+ if lang in ['english', 'hindi', 'telugu']:
404
+ session['language'] = lang
405
+ return jsonify({'success': True, 'language': lang})
406
+ return jsonify({'success': False, 'error': 'Invalid language'}), 400
407
+
408
+ @app.route('/upload', methods=['GET', 'POST'])
409
+ def upload():
410
+ """Upload and process leaf image"""
411
+ language = session.get('language', 'english')
412
+
413
+ if request.method == 'GET':
414
+ return render_template('upload.html',
415
+ lang=language,
416
+ translations=translations)
417
+
418
+ # POST request - handle file upload
419
+ try:
420
+ if 'leaf_image' not in request.files:
421
+ return jsonify({'error': 'No file uploaded'}), 400
422
+
423
+ file = request.files['leaf_image']
424
+ if file.filename == '':
425
+ return jsonify({'error': 'No file selected'}), 400
426
+
427
+ if not allowed_file(file.filename):
428
+ return jsonify({'error': 'Invalid file type. Please upload PNG, JPG, or JPEG'}), 400
429
+
430
+ # Save file with timestamp
431
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
432
+ filename = secure_filename(f"{timestamp}_{file.filename}")
433
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
434
+ file.save(filepath)
435
+
436
+ logger.info(f"File uploaded: {filename}")
437
+
438
+ # Predict disease
439
+ prediction = predict_disease(filepath)
440
+
441
+ # Store prediction in session for result page
442
+ session['prediction'] = prediction
443
+ session['image_filename'] = filename
444
+
445
+ return jsonify({
446
+ 'success': True,
447
+ 'redirect': '/result'
448
+ })
449
+
450
+ except Exception as e:
451
+ logger.error(f"Error processing upload: {str(e)}")
452
+ return jsonify({'error': 'Error processing image. Please try again.'}), 500
453
+
454
+ @app.route('/result')
455
+ def result():
456
+ """Display disease detection results"""
457
+ language = session.get('language', 'english')
458
+ prediction = session.get('prediction')
459
+ image_filename = session.get('image_filename')
460
+
461
+ if not prediction:
462
+ return redirect('/')
463
+
464
+ # Generate treatment advice
465
+ treatment = None
466
+ if not prediction.get('is_healthy', False) and prediction['confidence'] > 60:
467
+ location = request.args.get('location', '')
468
+ treatment = generate_treatment_advice(
469
+ crop_name=prediction['crop'],
470
+ disease_name=prediction['disease'],
471
+ language=language,
472
+ location=location
473
+ )
474
+
475
+ return render_template('result.html',
476
+ prediction=prediction,
477
+ treatment=treatment,
478
+ image_filename=image_filename,
479
+ lang=language,
480
+ translations=translations)
481
+
482
+ @app.route('/api/predict', methods=['POST'])
483
+ def api_predict():
484
+ """API endpoint for disease prediction"""
485
+ try:
486
+ if 'image' not in request.files:
487
+ return jsonify({'error': 'No image provided'}), 400
488
+
489
+ file = request.files['image']
490
+ if not allowed_file(file.filename):
491
+ return jsonify({'error': 'Invalid file type'}), 400
492
+
493
+ # Save and process
494
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
495
+ filename = secure_filename(f"api_{timestamp}_{file.filename}")
496
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
497
+ file.save(filepath)
498
+
499
+ # Predict
500
+ prediction = predict_disease(filepath)
501
+
502
+ # Generate treatment if requested
503
+ language = request.form.get('language', 'english')
504
+ include_treatment = request.form.get('include_treatment', 'true').lower() == 'true'
505
+
506
+ response = {
507
+ 'success': True,
508
+ 'prediction': prediction,
509
+ 'image_url': f"/static/uploads/{filename}"
510
+ }
511
+
512
+ if include_treatment and not prediction.get('is_healthy', False):
513
+ treatment = generate_treatment_advice(
514
+ crop_name=prediction['crop'],
515
+ disease_name=prediction['disease'],
516
+ language=language
517
+ )
518
+ response['treatment'] = treatment
519
+
520
+ return jsonify(response)
521
+
522
+ except Exception as e:
523
+ logger.error(f"API prediction error: {str(e)}")
524
+ return jsonify({'error': 'Prediction failed'}), 500
525
+
526
+ @app.route('/health')
527
+ def health():
528
+ """Health check endpoint"""
529
+ return jsonify({
530
+ 'status': 'healthy',
531
+ 'model_loaded': model is not None,
532
+ 'model_exists': os.path.exists(MODEL_PATH),
533
+ 'translations_loaded': translations is not None,
534
+ 'device': str(device) if device else 'not set',
535
+ 'num_classes': len(class_labels) if class_labels else 0
536
+ })
537
+
538
+ @app.errorhandler(413)
539
+ def request_entity_too_large(error):
540
+ """Handle file too large error"""
541
+ return jsonify({'error': 'File too large. Maximum size is 16MB'}), 413
542
+
543
+ @app.errorhandler(500)
544
+ def internal_error(error):
545
+ """Handle internal server errors"""
546
+ logger.error(f"Internal error: {str(error)}")
547
+ return jsonify({'error': 'Internal server error'}), 500
548
+
549
+ if __name__ == '__main__':
550
+ # Load resources on startup
551
+ load_resources()
552
+
553
+ # Run app
554
+ port = int(os.environ.get('PORT', 5000))
555
+ debug = os.environ.get('FLASK_ENV') == 'development'
556
+ app.run(host='0.0.0.0', port=port, debug=debug)
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Flask dependencies
2
+ Flask==3.0.0
3
+ Werkzeug==3.0.1
4
+
5
+ # PyTorch and Image Processing
6
+ torch>=2.5.0
7
+ torchvision>=0.20.0
8
+ Pillow==11.0.0
9
+ numpy>=1.24.0,<2.0.0
10
+
11
+ # HTTP requests for OpenRouter API
12
+ requests==2.31.0
13
+
14
+ # Environment and utilities
15
+ python-dotenv==1.0.0
16
+ gunicorn==21.2.0
17
+
18
+ # Optional but recommended
19
+ redis==5.0.1
20
+ celery==5.3.4