sikeaditya commited on
Commit
7154df8
·
verified ·
1 Parent(s): 48cb560

Upload 7 files

Browse files
Files changed (7) hide show
  1. .gitignore +2 -0
  2. Dockerfile +9 -0
  3. app.py +437 -0
  4. feedback.json +7 -0
  5. requirements.txt +5 -0
  6. templates/index.html +143 -0
  7. templates/results.html +339 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /AIO
2
+ .env
Dockerfile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
4
+ import requests
5
+ from werkzeug.utils import secure_filename
6
+ import google.generativeai as genai
7
+ from dotenv import load_dotenv
8
+ import base64
9
+ import json
10
+ from datetime import datetime, timedelta
11
+ import threading
12
+ import time
13
+
14
+ # Load environment variables
15
+ load_dotenv()
16
+
17
+ # Configure the Gemini API
18
+ # Try to get API key from environment variable, first from HF_SPACES then from .env file
19
+ # GEMINI_API_KEY = os.getenv("HF_GEMINI_API_KEY") or os.getenv("GEMINI_API_KEY")
20
+ GEMINI_API_KEY = os.getenv("HF_GEMINI_API_KEY") or os.getenv("GEMINI_API_KEY")
21
+ if not GEMINI_API_KEY:
22
+ raise ValueError("Google API Key not found. Set it as GEMINI_API_KEY in the Space settings.")
23
+
24
+ genai.configure(api_key=GEMINI_API_KEY)
25
+
26
+ # Setup the Gemini model
27
+ model = genai.GenerativeModel('gemini-1.5-flash')
28
+
29
+ app = Flask(__name__)
30
+ app.secret_key = os.getenv("SECRET_KEY", "your-default-secret-key-for-flash-messages")
31
+
32
+ # Configure upload folder
33
+ UPLOAD_FOLDER = 'static/uploads'
34
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
35
+
36
+ if not os.path.exists(UPLOAD_FOLDER):
37
+ os.makedirs(UPLOAD_FOLDER)
38
+
39
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
40
+
41
+ def allowed_file(filename):
42
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
43
+
44
+ def encode_image(image_path):
45
+ with open(image_path, "rb") as image_file:
46
+ return base64.b64encode(image_file.read()).decode('utf-8')
47
+
48
+ def get_web_pesticide_info(disease, plant_type="Unknown"):
49
+ """Fetch pesticide information from web sources for a specific disease and plant type"""
50
+ query = f"site:agrowon.esakal.com {disease} in {plant_type}"
51
+ url = "https://www.googleapis.com/customsearch/v1"
52
+ params = {
53
+ "key": os.getenv("GOOGLE_API_KEY"),
54
+ "cx": os.getenv("GOOGLE_CX"),
55
+ "q": query,
56
+ "num": 3
57
+ }
58
+ try:
59
+ response = requests.get(url, params=params)
60
+ response.raise_for_status()
61
+ data = response.json()
62
+ if "items" in data and len(data["items"]) > 0:
63
+ item = data["items"][0]
64
+ return {
65
+ "title": item.get("title", "No title available"),
66
+ "link": item.get("link", "#"),
67
+ "snippet": item.get("snippet", "No snippet available"),
68
+ "summary": item.get("snippet", "No snippet available")
69
+ }
70
+ except Exception as e:
71
+ print(f"Error retrieving web pesticide info: {str(e)}")
72
+ return None
73
+
74
+ def get_more_web_info(query):
75
+ """Get more general web information based on a search query"""
76
+ url = "https://www.googleapis.com/customsearch/v1"
77
+ params = {
78
+ "key": os.getenv("GOOGLE_API_KEY"),
79
+ "cx": os.getenv("GOOGLE_CX"),
80
+ "q": query,
81
+ "num": 3
82
+ }
83
+ try:
84
+ response = requests.get(url, params=params)
85
+ response.raise_for_status()
86
+ data = response.json()
87
+ results = []
88
+ if "items" in data:
89
+ for item in data["items"]:
90
+ results.append({
91
+ "title": item.get("title", "No title available"),
92
+ "link": item.get("link", "#"),
93
+ "snippet": item.get("snippet", "No snippet available")
94
+ })
95
+ return results
96
+ except Exception as e:
97
+ print(f"Error retrieving additional articles: {str(e)}")
98
+ return []
99
+
100
+ def get_commercial_product_info(recommendation, disease_name):
101
+ """Fetch commercial product information related to a pesticide recommendation.
102
+ If no relevant products are found from web sources, return default products based on issue type:
103
+ bacterial, fungicide (disease), or insecticide.
104
+ """
105
+ indiamart_query = f"site:indiamart.com pesticide '{disease_name}' '{recommendation}'"
106
+ krishi_query = f"site:krishisevakendra.in/products pesticide '{disease_name}' '{recommendation}'"
107
+
108
+ indiamart_results = get_more_web_info(indiamart_query)
109
+ krishi_results = get_more_web_info(krishi_query)
110
+
111
+ # Merge results from both sources
112
+ results = indiamart_results + krishi_results
113
+
114
+ if not results:
115
+ lower_disease = disease_name.lower()
116
+ lower_recommendation = recommendation.lower()
117
+
118
+ # Bacterial fallback
119
+ if ("bacteria" in lower_disease or "bacterial" in lower_disease or
120
+ "bacteria" in lower_recommendation or "bacterial" in lower_recommendation):
121
+ results = [
122
+ {
123
+ "title": "UPL SAAF Carbendazin Mancozeb Bactericide",
124
+ "link": "https://www.amazon.in/UPL-SAAF-Carbendazinm12-Mancozeb63-Action/dp/B0DJLQRL44?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A1BIAFERY87G8Q",
125
+ "snippet": "Bactericide for controlling bacterial infections."
126
+ },
127
+ {
128
+ "title": "Tropical Tagmycin Bactericide",
129
+ "link": "https://krushidukan.bharatagri.com/en/products/tropical-tagmycin-bactericide?variant=46519705895155&country=IN&currency=INR&utm_medium=product_sync&utm_source=google&utm_content=sag_organic&utm_campaign=sag_organic&srsltid=AfmBOoptFf8O3lpleZBgvI7pIOYUnHP6EWoZ-M6vGZ2er8VYU2PzVbkc7sc",
130
+ "snippet": "Bactericide for effective bacterial infection management."
131
+ }
132
+ ]
133
+ # Fungicide / Disease fallback
134
+ elif ("fungus" in lower_disease or "fungicide" in lower_recommendation or
135
+ "antibiotic" in lower_recommendation or "disease" in lower_disease):
136
+ results = [
137
+ {
138
+ "title": "Plantomycin Bio Organic Antibiotic Effective Disease",
139
+ "link": "https://www.amazon.in/Plantomycin-Bio-Organic-Antibiotic-Effective-Disease/dp/B0DRVVJKQ4?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A2PDMX630A5EG6",
140
+ "snippet": "Bio organic antibiotic for effective control of plant diseases."
141
+ },
142
+ {
143
+ "title": "WET-TREE Larvicide Thuringiensis Insecticide",
144
+ "link": "https://www.amazon.in/WET-TREE-Larvicide-Thuringiensis-Insecticide/dp/B0D6R72KHV?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3V4YZ24A56I42",
145
+ "snippet": "Larvicide with thuringiensis for disease prevention."
146
+ },
147
+ {
148
+ "title": "WET-TREE Larvicide Thuringiensis Insecticide",
149
+ "link": "https://www.amazon.in/WET-TREE-Larvicide-Thuringiensis-Insecticide/dp/B0D6R72KHV?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3V4YZ24A56I42",
150
+ "snippet": "Larvicide with thuringiensis for disease prevention."
151
+ }
152
+ ]
153
+ # Insecticide fallback
154
+ elif ("insecticide" in lower_disease or "insect" in lower_disease or "pest" in lower_disease or
155
+ "insecticide" in lower_recommendation or "insect" in lower_recommendation or "pest" in lower_recommendation):
156
+ results = [
157
+ {
158
+ "title": "Syngenta Actara Insecticide",
159
+ "link": "https://www.amazon.in/syngenta-Actara-Insect-Repellent-Insecticide/dp/B08W55XTHS?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3ABQWNNCUI42M",
160
+ "snippet": "Effective systemic insecticide for pest control."
161
+ },
162
+ {
163
+ "title": "Cyhalothrin Insecticide",
164
+ "link": "https://www.amazon.in/Cyhalothrin-Control-Eradication-Mosquitoes-Crawling/dp/B01N53VH1T?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A1ZSKCLHK592D5",
165
+ "snippet": "Broad-spectrum insecticide for pest management."
166
+ }
167
+ ]
168
+ # Default fallback to insecticide if none of the above match
169
+ else:
170
+ results = [
171
+ {
172
+ "title": "Syngenta Actara Insecticide",
173
+ "link": "https://www.amazon.in/syngenta-Actara-Insect-Repellent-Insecticide/dp/B08W55XTHS?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3ABQWNNCUI42M",
174
+ "snippet": "Effective systemic insecticide for pest control."
175
+ },
176
+ {
177
+ "title": "Cyhalothrin Insecticide",
178
+ "link": "https://www.amazon.in/Cyhalothrin-Control-Eradication-Mosquitoes-Crawling/dp/B01N53VH1T?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A1ZSKCLHK592D5",
179
+ "snippet": "Broad-spectrum insecticide for pest management."
180
+ }
181
+ ]
182
+
183
+ return results
184
+
185
+
186
+ def get_relevant_feedback(plant_name):
187
+ """Retrieve feedback entries relevant to the given plant name from feedback.json."""
188
+ feedback_file = "feedback.json"
189
+ if os.path.exists(feedback_file):
190
+ try:
191
+ with open(feedback_file, "r") as f:
192
+ all_feedback = json.load(f)
193
+ # Filter feedback where the plant_name matches (case-insensitive)
194
+ relevant = [entry.get("feedback") for entry in all_feedback if entry.get("plant_name", "").lower() == plant_name.lower()]
195
+ # Optionally limit to a few items (e.g., first 3)
196
+ if relevant:
197
+ return " ".join(relevant[:3])
198
+ except Exception as e:
199
+ print(f"Error reading feedback for reinforcement: {e}")
200
+ return ""
201
+
202
+
203
+ def analyze_plant_image(image_path, plant_name, language):
204
+ try:
205
+ # Load the image
206
+ image_parts = [
207
+ {
208
+ "mime_type": "image/jpeg",
209
+ "data": encode_image(image_path)
210
+ }
211
+ ]
212
+
213
+ # Load relevant feedback (reinforcement data) for this plant
214
+ feedback_context = get_relevant_feedback(plant_name)
215
+ feedback_instruction = f" Please consider the following user feedback from similar cases: {feedback_context}" if feedback_context else ""
216
+
217
+ # Create prompt for Gemini API with language instruction and feedback reinforcement if available
218
+ prompt = f"""
219
+ Analyze this image of a {plant_name} plant and prioritize determining if it's healthy or has a disease or pest infestation.
220
+
221
+ If a disease or pest is detected, remember the plant can be healthy too. Provide the following information in JSON format:
222
+ Check the {feedback_instruction} and then make the response after learning from that responses give around 60% weightage to the feedbck data.
223
+ {{
224
+ "results": [
225
+ {{
226
+ "type": "disease/pest",
227
+ "name": "Name of disease or pest",
228
+ "probability": "Probability as a percentage",
229
+ "symptoms": "Describe the visible symptoms",
230
+ "causes": "Main causes of the disease or pest",
231
+ "severity": "Low/Medium/High",
232
+ "spreading": "How it spreads",
233
+ "treatment": "Treatment options",
234
+ "prevention": "Preventive measures"
235
+ }},
236
+ {{
237
+ // Second most likely disease/pest with the same structure
238
+ }},
239
+ {{
240
+ // Third most likely disease/pest with the same structure
241
+ }}
242
+ ],
243
+ "is_healthy": boolean indicating if the plant appears healthy,
244
+ "confidence": "Overall confidence in the analysis as a percentage"
245
+ }}
246
+
247
+ Only return the JSON data and nothing else. Ensure the JSON is valid and properly formatted.
248
+ If the plant appears completely healthy, set is_healthy to true and include an empty results array.
249
+ Additionally, provide the response in {language} language.
250
+ and at end show which all data from feedback was taken into consederation and if no data was taken so no data.
251
+ """
252
+
253
+ # Send request to Gemini API
254
+ response = model.generate_content([prompt] + image_parts)
255
+
256
+ # Extract the JSON response
257
+ response_text = response.text
258
+
259
+ # Find JSON within response text if needed
260
+ json_start = response_text.find('{')
261
+ json_end = response_text.rfind('}') + 1
262
+
263
+ if json_start >= 0 and json_end > 0:
264
+ json_str = response_text[json_start:json_end]
265
+ return json.loads(json_str)
266
+ else:
267
+ # Return a default response if JSON parsing fails
268
+ return {
269
+ "error": "Failed to parse the API response",
270
+ "raw_response": response_text
271
+ }
272
+
273
+ except Exception as e:
274
+ return {
275
+ "error": str(e),
276
+ "is_healthy": None,
277
+ "results": []
278
+ }
279
+
280
+
281
+ def cleanup_old_files(directory, max_age_hours=1): # Reduced to 1 hour for Hugging Face
282
+ """Remove files older than the specified age from the directory"""
283
+ while True:
284
+ now = datetime.now()
285
+ for filename in os.listdir(directory):
286
+ if filename == '.gitkeep': # Skip the .gitkeep file
287
+ continue
288
+
289
+ file_path = os.path.join(directory, filename)
290
+ file_age = now - datetime.fromtimestamp(os.path.getctime(file_path))
291
+ if file_age > timedelta(hours=max_age_hours):
292
+ try:
293
+ os.remove(file_path)
294
+ print(f"Removed old file: {file_path}")
295
+ except Exception as e:
296
+ print(f"Error removing {file_path}: {e}")
297
+ # Sleep for 5 minutes before checking again
298
+ time.sleep(300) # 5 minutes
299
+
300
+ @app.route('/', methods=['GET'])
301
+ def index():
302
+ # GET request - show the upload form
303
+ return render_template('index.html', show_results=False)
304
+
305
+ @app.route('/feedback', methods=['POST'])
306
+ def feedback():
307
+ # Get feedback from form submission
308
+ feedback_text = request.form.get("feedback")
309
+ plant_name = request.form.get("plant_name", "Unknown") # Optional: include plant name for context
310
+
311
+ if not feedback_text:
312
+ flash("Please provide your feedback before submitting.")
313
+ return redirect(url_for('index'))
314
+
315
+ # Create a feedback record with a timestamp
316
+ feedback_data = {
317
+ "plant_name": plant_name,
318
+ "feedback": feedback_text,
319
+ "timestamp": datetime.now().isoformat()
320
+ }
321
+
322
+ # Define the file to store feedback
323
+ feedback_file = "feedback.json"
324
+
325
+ # Load existing feedback (if any)
326
+ if os.path.exists(feedback_file):
327
+ try:
328
+ with open(feedback_file, "r") as f:
329
+ existing_feedback = json.load(f)
330
+ except Exception as e:
331
+ print(f"Error reading feedback file: {e}")
332
+ existing_feedback = []
333
+ else:
334
+ existing_feedback = []
335
+
336
+ # Append the new feedback and save it back to file
337
+ existing_feedback.append(feedback_data)
338
+ try:
339
+ with open(feedback_file, "w") as f:
340
+ json.dump(existing_feedback, f, indent=4)
341
+ except Exception as e:
342
+ flash(f"Error saving your feedback: {str(e)}")
343
+ return redirect(url_for('index'))
344
+
345
+ flash("Thank you for your feedback!")
346
+ return redirect(url_for('index'))
347
+
348
+ @app.route('/analyze', methods=['POST'])
349
+ def analyze():
350
+ if 'plant_image' not in request.files:
351
+ flash('No file part')
352
+ return redirect(url_for('index'))
353
+
354
+ file = request.files['plant_image']
355
+ plant_name = request.form.get('plant_name', 'unknown')
356
+ language = request.form.get('language', 'English') # New field for response language
357
+
358
+ if file.filename == '':
359
+ flash('No selected file')
360
+ return redirect(url_for('index'))
361
+
362
+ if file and allowed_file(file.filename):
363
+ # Generate a unique filename to avoid collisions
364
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
365
+ original_filename = secure_filename(file.filename)
366
+ filename = f"{timestamp}_{original_filename}"
367
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
368
+ file.save(file_path)
369
+
370
+ try:
371
+ # Analyze the image with language parameter
372
+ analysis_result = analyze_plant_image(file_path, plant_name, language)
373
+
374
+ if 'error' in analysis_result:
375
+ flash(f"Error analyzing image: {analysis_result['error']}")
376
+ if os.path.exists(file_path):
377
+ os.remove(file_path)
378
+ return redirect(url_for('index'))
379
+
380
+ # Get additional web information for detected diseases/pests
381
+ web_info = {}
382
+ product_info = {}
383
+
384
+ if not analysis_result.get('is_healthy', False) and 'results' in analysis_result:
385
+ for result in analysis_result['results']:
386
+ disease_name = result.get('name', '')
387
+ if disease_name:
388
+ web_info[disease_name] = get_web_pesticide_info(disease_name, plant_name)
389
+ treatment = result.get('treatment', '')
390
+ if treatment:
391
+ product_info[disease_name] = get_commercial_product_info(treatment, disease_name)
392
+
393
+ response = render_template(
394
+ 'results.html',
395
+ results=analysis_result,
396
+ plant_name=plant_name,
397
+ image_path=file_path.replace('static/', '', 1),
398
+ web_info=web_info,
399
+ product_info=product_info
400
+ )
401
+
402
+ def delete_file_after_delay(path, delay=30):
403
+ time.sleep(delay)
404
+ if os.path.exists(path):
405
+ try:
406
+ os.remove(path)
407
+ print(f"Deleted analyzed file: {path}")
408
+ except Exception as e:
409
+ print(f"Error deleting {path}: {e}")
410
+
411
+ threading.Thread(
412
+ target=delete_file_after_delay,
413
+ args=(file_path,),
414
+ daemon=True
415
+ ).start()
416
+
417
+ return response
418
+
419
+ except Exception as e:
420
+ flash(f"An error occurred: {str(e)}")
421
+ if os.path.exists(file_path):
422
+ os.remove(file_path)
423
+ return redirect(url_for('index'))
424
+
425
+ flash('Invalid file type. Please upload an image (png, jpg, jpeg, gif).')
426
+ return redirect(url_for('index'))
427
+
428
+
429
+ # Hugging Face Spaces requires the app to be available on port 7860
430
+ if __name__ == '__main__':
431
+ # Start the cleanup thread when the app starts
432
+ cleanup_thread = threading.Thread(target=cleanup_old_files, args=(app.config['UPLOAD_FOLDER'],), daemon=True)
433
+ cleanup_thread.start()
434
+
435
+ # Get the port from environment variable for Hugging Face Spaces compatibility
436
+ port = int(os.environ.get("PORT", 7860))
437
+ app.run(host='0.0.0.0', port=port)
feedback.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "plant_name": "soyabean",
4
+ "feedback": "Brown Spot is a wrong prediction it is actually bacterial blight ",
5
+ "timestamp": "2025-03-01T12:49:14.413106"
6
+ }
7
+ ]
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ flask==2.0.1
2
+ Werkzeug==2.0.1
3
+ google-generativeai==0.3.2
4
+ python-dotenv==0.19.1
5
+ gunicorn==20.1.0
templates/index.html ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/index.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Plant Disease Detector</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <style>
10
+ body {
11
+ background-color: #f8f9fa;
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
+ }
14
+ .hero-section {
15
+ background-color: #28a745;
16
+ color: white;
17
+ padding: 3rem 0;
18
+ margin-bottom: 2rem;
19
+ }
20
+ .upload-container {
21
+ background-color: white;
22
+ border-radius: 10px;
23
+ padding: 2rem;
24
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
25
+ }
26
+ .instructions {
27
+ background-color: #e9f7ef;
28
+ border-left: 4px solid #28a745;
29
+ padding: 1rem;
30
+ margin-bottom: 1.5rem;
31
+ }
32
+ .preview-container {
33
+ max-width: 300px;
34
+ max-height: 300px;
35
+ overflow: hidden;
36
+ margin: 0 auto;
37
+ border: 2px dashed #dee2e6;
38
+ border-radius: 5px;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ }
43
+ #imagePreview {
44
+ max-width: 100%;
45
+ max-height: 100%;
46
+ display: none;
47
+ }
48
+ </style>
49
+ </head>
50
+ <body>
51
+ <div class="hero-section">
52
+ <div class="container text-center">
53
+ <h1 class="display-4">Plant Disease & Pest Detector</h1>
54
+ <p class="lead">Upload an image of your plant to detect diseases and pests</p>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="container mb-5">
59
+ {% with messages = get_flashed_messages() %}
60
+ {% if messages %}
61
+ {% for message in messages %}
62
+ <div class="alert alert-warning alert-dismissible fade show" role="alert">
63
+ {{ message }}
64
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
65
+ </div>
66
+ {% endfor %}
67
+ {% endif %}
68
+ {% endwith %}
69
+
70
+ <div class="row justify-content-center">
71
+ <div class="col-md-8">
72
+ <div class="upload-container">
73
+ <div class="instructions">
74
+ <h5>How to use:</h5>
75
+ <ol>
76
+ <li>Select an image of your plant (preferably showing affected areas)</li>
77
+ <li>Enter the name of the plant (e.g., Tomato, Rice, Potato)</li>
78
+ <li>Click "Analyze Plant" to detect diseases or pests</li>
79
+ </ol>
80
+ </div>
81
+
82
+ <form action="/analyze" method="post" enctype="multipart/form-data">
83
+ <div class="mb-4">
84
+ <div class="preview-container mb-3">
85
+ <img id="imagePreview" src="#" alt="Image Preview">
86
+ <span id="previewPlaceholder">Image preview will appear here</span>
87
+ </div>
88
+ <label for="plant_image" class="form-label">Upload Plant Image</label>
89
+ <input class="form-control" type="file" id="plant_image" name="plant_image" accept="image/*" required onchange="previewImage(event)">
90
+ </div>
91
+
92
+ <div class="mb-4">
93
+ <label for="plant_name" class="form-label">Plant Name</label>
94
+ <input type="text" class="form-control" id="plant_name" name="plant_name" placeholder="e.g., Tomato, Rice, Potato" required>
95
+ </div>
96
+
97
+ <div class="mb-4">
98
+ <label for="language" class="form-label">Response Language</label>
99
+ <select class="form-control" id="language" name="language" required>
100
+ <option value="English">English</option>
101
+ <option value="Hindi">Hindi</option>
102
+ <option value="Bengali">Bengali</option>
103
+ <option value="Telugu">Telugu</option>
104
+ <option value="Marathi">Marathi</option>
105
+ <option value="Tamil">Tamil</option>
106
+ <option value="Gujarati">Gujarati</option>
107
+ <option value="Urdu">Urdu</option>
108
+ <option value="Kannada">Kannada</option>
109
+ <option value="Odia">Odia</option>
110
+ <option value="Malayalam">Malayalam</option>
111
+ </select>
112
+ </div>
113
+
114
+ <div class="d-grid">
115
+ <button type="submit" class="btn btn-success btn-lg">Analyze Plant</button>
116
+ </div>
117
+ </form>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <footer class="bg-dark text-white text-center py-3">
124
+ <div class="container">
125
+ <p class="mb-0">Plant Disease Detector &copy; 2025 | Powered by Gemini AI</p>
126
+ </div>
127
+ </footer>
128
+
129
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
130
+ <script>
131
+ function previewImage(event) {
132
+ var reader = new FileReader();
133
+ reader.onload = function() {
134
+ var output = document.getElementById('imagePreview');
135
+ output.src = reader.result;
136
+ output.style.display = 'block';
137
+ document.getElementById('previewPlaceholder').style.display = 'none';
138
+ };
139
+ reader.readAsDataURL(event.target.files[0]);
140
+ }
141
+ </script>
142
+ </body>
143
+ </html>
templates/results.html ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/results.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Plant Analysis Results</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <style>
10
+ body {
11
+ background-color: #f8f9fa;
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
+ }
14
+ .header-section {
15
+ background-color: #28a745;
16
+ color: white;
17
+ padding: 2rem 0;
18
+ margin-bottom: 2rem;
19
+ }
20
+ .result-container {
21
+ background-color: white;
22
+ border-radius: 10px;
23
+ padding: 2rem;
24
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
25
+ margin-bottom: 2rem;
26
+ }
27
+ .image-container {
28
+ text-align: center;
29
+ margin-bottom: 2rem;
30
+ }
31
+ .image-container img {
32
+ max-height: 400px;
33
+ max-width: 100%;
34
+ border-radius: 8px;
35
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
36
+ }
37
+ .result-card {
38
+ border-left: 4px solid;
39
+ margin-bottom: 1.5rem;
40
+ padding: 1rem;
41
+ border-radius: 5px;
42
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
43
+ }
44
+ .healthy {
45
+ background-color: #d4edda;
46
+ border-left-color: #28a745;
47
+ }
48
+ .severity-low {
49
+ background-color: #e7f5e9;
50
+ border-left-color: #3cb371;
51
+ }
52
+ .severity-medium {
53
+ background-color: #fff3cd;
54
+ border-left-color: #ffc107;
55
+ }
56
+ .severity-high {
57
+ background-color: #f8d7da;
58
+ border-left-color: #dc3545;
59
+ }
60
+ .info-row {
61
+ display: flex;
62
+ margin-bottom: 0.5rem;
63
+ }
64
+ .info-label {
65
+ font-weight: bold;
66
+ width: 120px;
67
+ flex-shrink: 0;
68
+ }
69
+ .info-content {
70
+ flex-grow: 1;
71
+ }
72
+ .probability-bar {
73
+ height: 8px;
74
+ background-color: #e9ecef;
75
+ border-radius: 4px;
76
+ margin-top: 5px;
77
+ overflow: hidden;
78
+ }
79
+ .probability-fill {
80
+ height: 100%;
81
+ border-radius: 4px;
82
+ background-color: #28a745;
83
+ }
84
+ .product-card {
85
+ height: 100%;
86
+ transition: transform 0.3s;
87
+ }
88
+ .product-card:hover {
89
+ transform: translateY(-5px);
90
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
91
+ }
92
+ .info-source {
93
+ font-size: 0.8rem;
94
+ color: #6c757d;
95
+ margin-top: 0.5rem;
96
+ }
97
+ .resource-badge {
98
+ position: absolute;
99
+ top: 10px;
100
+ right: 10px;
101
+ font-size: 0.7rem;
102
+ }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <div class="header-section">
107
+ <div class="container text-center">
108
+ <h1 class="display-5">Plant Analysis Results</h1>
109
+ <p class="lead">Analysis for {{ plant_name }}</p>
110
+
111
+ </div>
112
+ </div>
113
+
114
+ <div class="container mb-5">
115
+ <div class="row">
116
+ <div class="col-md-4">
117
+ <div class="image-container">
118
+ <img src="{{ url_for('static', filename=image_path) }}" alt="{{ plant_name }} Image" class="img-fluid">
119
+ <div class="mt-3">
120
+ <h5>{{ plant_name }}</h5>
121
+ {% if results.is_healthy %}
122
+ <span class="badge bg-success">Healthy</span>
123
+ {% else %}
124
+ <span class="badge bg-warning">Issues Detected</span>
125
+ {% endif %}
126
+ </div>
127
+ </div>
128
+
129
+ <div class="result-container">
130
+ <h4>Analysis Summary</h4>
131
+ <div class="info-row">
132
+ <div class="info-label">Plant:</div>
133
+ <div class="info-content">{{ plant_name }}</div>
134
+ </div>
135
+ <div class="info-row">
136
+ <div class="info-label">Status:</div>
137
+ <div class="info-content">
138
+ {% if results.is_healthy %}
139
+ <span class="text-success">Healthy</span>
140
+ {% else %}
141
+ <span class="text-warning">Issues Detected</span>
142
+ {% endif %}
143
+ </div>
144
+ </div>
145
+ {% if results.confidence %}
146
+ <div class="info-row">
147
+ <div class="info-label">Confidence:</div>
148
+ <div class="info-content">{{ results.confidence }}</div>
149
+ </div>
150
+ {% endif %}
151
+
152
+ <div class="d-grid gap-2 mt-4">
153
+ <a href="{{ url_for('index') }}" class="btn btn-primary">Analyze Another Plant</a>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="col-md-8">
159
+ {% if results.is_healthy %}
160
+ <div class="result-container healthy">
161
+ <h3 class="text-success">Good News!</h3>
162
+ <p class="lead">Your {{ plant_name }} plant appears to be healthy. Continue with your current care practices.</p>
163
+
164
+ <h5 class="mt-4">Recommendations:</h5>
165
+ <ul>
166
+ <li>Maintain regular watering schedule</li>
167
+ <li>Continue with balanced fertilization</li>
168
+ <li>Monitor for any changes in appearance</li>
169
+ <li>Ensure proper sunlight exposure</li>
170
+ <li>Practice preventive measures against common pests and diseases</li>
171
+ </ul>
172
+ </div>
173
+ {% elif results.results %}
174
+ <div class="result-container">
175
+ <h3>Detected Issues</h3>
176
+ <p>Here are the potential issues detected in your {{ plant_name }} plant:</p>
177
+
178
+ {% for result in results.results %}
179
+ <div class="result-card severity-{{ result.severity|lower }}">
180
+ <div class="d-flex justify-content-between align-items-center mb-2">
181
+ <h4>{{ loop.index }}. {{ result.name }}</h4>
182
+ <div>
183
+ <span class="badge bg-info me-2">{{ result.type }}</span>
184
+ <span class="badge {% if result.severity|lower == 'low' %}bg-success{% elif result.severity|lower == 'medium' %}bg-warning{% else %}bg-danger{% endif %}">
185
+ {{ result.severity }} Severity
186
+ </span>
187
+ </div>
188
+ </div>
189
+
190
+ <div class="info-row">
191
+ <div class="info-label">Probability:</div>
192
+ <div class="info-content">
193
+ {{ result.probability }}
194
+ <div class="probability-bar">
195
+ {% set prob_value = result.probability|replace('%', '')|float %}
196
+ <div class="probability-fill" style="width: {{ prob_value }}%;"></div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <div class="info-row">
202
+ <div class="info-label">Symptoms:</div>
203
+ <div class="info-content">{{ result.symptoms }}</div>
204
+ </div>
205
+
206
+ <div class="info-row">
207
+ <div class="info-label">Causes:</div>
208
+ <div class="info-content">{{ result.causes }}</div>
209
+ </div>
210
+
211
+ <div class="info-row">
212
+ <div class="info-label">Spreading:</div>
213
+ <div class="info-content">{{ result.spreading }}</div>
214
+ </div>
215
+
216
+ <div class="mt-3">
217
+ <h5>Treatment:</h5>
218
+ <p>{{ result.treatment }}</p>
219
+ </div>
220
+
221
+ <div class="mt-3">
222
+ <h5>Prevention:</h5>
223
+ <p>{{ result.prevention }}</p>
224
+ </div>
225
+
226
+ {% if web_info and result.name in web_info and web_info[result.name] %}
227
+ <div class="mt-4">
228
+ <h5>Additional Information:</h5>
229
+ <div class="card">
230
+ <div class="card-body">
231
+ <h6 class="card-title">{{ web_info[result.name].title }}</h6>
232
+ <p class="card-text">{{ web_info[result.name].summary }}</p>
233
+ <div class="d-flex justify-content-between">
234
+ <a href="{{ web_info[result.name].link }}" class="btn btn-sm btn-outline-success" target="_blank">Read More</a>
235
+ <span class="info-source">Source: Agrowon</span>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ {% endif %}
241
+
242
+ {% if product_info and result.name in product_info and product_info[result.name] %}
243
+ <div class="mt-4">
244
+ <h5>Recommended Products:</h5>
245
+ <div class="row">
246
+ {% for product in product_info[result.name][:2] %}
247
+ <div class="col-md-6 mb-2">
248
+ <div class="card product-card">
249
+ <div class="card-body">
250
+ <span class="badge bg-secondary resource-badge">
251
+ {% if 'indiamart' in product.link %}IndiaMart{% else %}Krishi Seva{% endif %}
252
+ </span>
253
+ <h6 class="card-title" style="font-size: 0.9rem;">{{ product.title }}</h6>
254
+ <p class="card-text small">{{ product.snippet[:100] }}{% if product.snippet|length > 100 %}...{% endif %}</p>
255
+ <a href="{{ product.link }}" class="btn btn-sm btn-outline-primary" target="_blank">View Product</a>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ {% endfor %}
260
+ </div>
261
+ {% if product_info[result.name]|length > 2 %}
262
+ <div class="text-center mt-2">
263
+ <button class="btn btn-sm btn-link" type="button" data-bs-toggle="collapse" data-bs-target="#moreProducts{{ loop.index }}" aria-expanded="false">
264
+ Show more products <i class="bi bi-chevron-down"></i>
265
+ </button>
266
+ <div class="collapse" id="moreProducts{{ loop.index }}">
267
+ <div class="row mt-2">
268
+ {% for product in product_info[result.name][2:] %}
269
+ <div class="col-md-6 mb-2">
270
+ <div class="card product-card">
271
+ <div class="card-body">
272
+ <span class="badge bg-secondary resource-badge">
273
+ {% if 'indiamart' in product.link %}IndiaMart{% else %}Krishi Seva{% endif %}
274
+ </span>
275
+ <h6 class="card-title" style="font-size: 0.9rem;">{{ product.title }}</h6>
276
+ <p class="card-text small">{{ product.snippet[:100] }}{% if product.snippet|length > 100 %}...{% endif %}</p>
277
+ <a href="{{ product.link }}" class="btn btn-sm btn-outline-primary" target="_blank">View Product</a>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ {% endfor %}
282
+ </div>
283
+ </div>
284
+ </div>
285
+ {% endif %}
286
+ </div>
287
+ {% endif %}
288
+ </div>
289
+ {% endfor %}
290
+
291
+ <div class="mt-4 alert alert-info">
292
+ <h5 class="alert-heading">Disclaimer</h5>
293
+ <p>The product recommendations are provided for informational purposes only. Always consult with a local agricultural expert for specific recommendations suited to your region and conditions.</p>
294
+ </div>
295
+ <div class="container mb-5">
296
+ <div class="result-container">
297
+ <h4>Send us your Feedback</h4>
298
+ <p>We would love to hear your thoughts on the analysis.</p>
299
+ <form action="/feedback" method="post">
300
+ <!-- Include plant name so feedback is context aware -->
301
+ <input type="hidden" name="plant_name" value="{{ plant_name }}">
302
+ <div class="mb-3">
303
+ <textarea class="form-control" name="feedback" rows="4" placeholder="Your feedback here..." required></textarea>
304
+ </div>
305
+ <button type="submit" class="btn btn-primary">Submit Feedback</button>
306
+ </form>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ {% else %}
311
+ <div class="result-container">
312
+ <div class="alert alert-warning">
313
+ <h4 class="alert-heading">Analysis Inconclusive</h4>
314
+ <p>We couldn't determine with certainty whether your plant has issues. This could be due to:</p>
315
+ <ul>
316
+ <li>Image quality or lighting issues</li>
317
+ <li>Early stage symptoms that aren't clearly visible</li>
318
+ <li>A rare condition not in our database</li>
319
+ </ul>
320
+ <hr>
321
+ <p class="mb-0">Try uploading a clearer image or one that shows the affected areas more directly.</p>
322
+ </div>
323
+ </div>
324
+ {% endif %}
325
+ </div>
326
+ </div>
327
+ </div>
328
+
329
+ <footer class="bg-dark text-white text-center py-3">
330
+ <div class="container">
331
+ <p class="mb-0">Plant Disease Detector &copy; 2025 | Powered by Gemini AI</p>
332
+ </div>
333
+ </footer>
334
+
335
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
336
+ <!-- Add Bootstrap Icons for the dropdown arrows -->
337
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
338
+ </body>
339
+ </html>