# app.py import os from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session import requests from werkzeug.utils import secure_filename import google.generativeai as genai from dotenv import load_dotenv import base64 import json from datetime import datetime, timedelta import threading import time # Load environment variables load_dotenv() # Configure the Gemini API # Try to get API key from environment variable, first from HF_SPACES then from .env file # GEMINI_API_KEY = os.getenv("HF_GEMINI_API_KEY") or os.getenv("GEMINI_API_KEY") GEMINI_API_KEY = os.getenv("HF_GEMINI_API_KEY") or os.getenv("GEMINI_API_KEY") if not GEMINI_API_KEY: raise ValueError("Google API Key not found. Set it as GEMINI_API_KEY in the Space settings.") genai.configure(api_key=GEMINI_API_KEY) # Setup the Gemini model model = genai.GenerativeModel('gemini-1.5-flash') app = Flask(__name__) app.secret_key = os.getenv("SECRET_KEY", "your-default-secret-key-for-flash-messages") # Configure upload folder UPLOAD_FOLDER = 'static/uploads' ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode('utf-8') def get_web_pesticide_info(disease, plant_type="Unknown"): """Fetch pesticide information from web sources for a specific disease and plant type""" query = f"site:agrowon.esakal.com {disease} in {plant_type}" url = "https://www.googleapis.com/customsearch/v1" params = { "key": os.getenv("GOOGLE_API_KEY"), "cx": os.getenv("GOOGLE_CX"), "q": query, "num": 3 } try: response = requests.get(url, params=params) response.raise_for_status() data = response.json() if "items" in data and len(data["items"]) > 0: item = data["items"][0] return { "title": item.get("title", "No title available"), "link": item.get("link", "#"), "snippet": item.get("snippet", "No snippet available"), "summary": item.get("snippet", "No snippet available") } except Exception as e: print(f"Error retrieving web pesticide info: {str(e)}") return None def get_more_web_info(query): """Get more general web information based on a search query""" url = "https://www.googleapis.com/customsearch/v1" params = { "key": os.getenv("GOOGLE_API_KEY"), "cx": os.getenv("GOOGLE_CX"), "q": query, "num": 3 } try: response = requests.get(url, params=params) response.raise_for_status() data = response.json() results = [] if "items" in data: for item in data["items"]: results.append({ "title": item.get("title", "No title available"), "link": item.get("link", "#"), "snippet": item.get("snippet", "No snippet available") }) return results except Exception as e: print(f"Error retrieving additional articles: {str(e)}") return [] def get_commercial_product_info(recommendation, disease_name): """Fetch commercial product information related to a pesticide recommendation. If no relevant products are found from web sources, return default products based on issue type: bacterial, fungicide (disease), or insecticide. """ indiamart_query = f"site:indiamart.com pesticide '{disease_name}' '{recommendation}'" krishi_query = f"site:krishisevakendra.in/products pesticide '{disease_name}' '{recommendation}'" indiamart_results = get_more_web_info(indiamart_query) krishi_results = get_more_web_info(krishi_query) # Merge results from both sources results = indiamart_results + krishi_results if not results: lower_disease = disease_name.lower() lower_recommendation = recommendation.lower() # Bacterial fallback if ("bacteria" in lower_disease or "bacterial" in lower_disease or "bacteria" in lower_recommendation or "bacterial" in lower_recommendation): results = [ { "title": "UPL SAAF Carbendazin Mancozeb Bactericide", "link": "https://www.amazon.in/UPL-SAAF-Carbendazinm12-Mancozeb63-Action/dp/B0DJLQRL44?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A1BIAFERY87G8Q", "snippet": "Bactericide for controlling bacterial infections." }, { "title": "Tropical Tagmycin Bactericide", "link": "https://krushidukan.bharatagri.com/en/products/tropical-tagmycin-bactericide?variant=46519705895155&country=IN¤cy=INR&utm_medium=product_sync&utm_source=google&utm_content=sag_organic&utm_campaign=sag_organic&srsltid=AfmBOoptFf8O3lpleZBgvI7pIOYUnHP6EWoZ-M6vGZ2er8VYU2PzVbkc7sc", "snippet": "Bactericide for effective bacterial infection management." } ] # Fungicide / Disease fallback elif ("fungus" in lower_disease or "fungicide" in lower_recommendation or "antibiotic" in lower_recommendation or "disease" in lower_disease): results = [ { "title": "Plantomycin Bio Organic Antibiotic Effective Disease", "link": "https://www.amazon.in/Plantomycin-Bio-Organic-Antibiotic-Effective-Disease/dp/B0DRVVJKQ4?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A2PDMX630A5EG6", "snippet": "Bio organic antibiotic for effective control of plant diseases." }, { "title": "WET-TREE Larvicide Thuringiensis Insecticide", "link": "https://www.amazon.in/WET-TREE-Larvicide-Thuringiensis-Insecticide/dp/B0D6R72KHV?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3V4YZ24A56I42", "snippet": "Larvicide with thuringiensis for disease prevention." }, { "title": "WET-TREE Larvicide Thuringiensis Insecticide", "link": "https://www.amazon.in/WET-TREE-Larvicide-Thuringiensis-Insecticide/dp/B0D6R72KHV?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3V4YZ24A56I42", "snippet": "Larvicide with thuringiensis for disease prevention." } ] # Insecticide fallback elif ("insecticide" in lower_disease or "insect" in lower_disease or "pest" in lower_disease or "insecticide" in lower_recommendation or "insect" in lower_recommendation or "pest" in lower_recommendation): results = [ { "title": "Syngenta Actara Insecticide", "link": "https://www.amazon.in/syngenta-Actara-Insect-Repellent-Insecticide/dp/B08W55XTHS?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3ABQWNNCUI42M", "snippet": "Effective systemic insecticide for pest control." }, { "title": "Cyhalothrin Insecticide", "link": "https://www.amazon.in/Cyhalothrin-Control-Eradication-Mosquitoes-Crawling/dp/B01N53VH1T?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A1ZSKCLHK592D5", "snippet": "Broad-spectrum insecticide for pest management." } ] # Default fallback to insecticide if none of the above match else: results = [ { "title": "Syngenta Actara Insecticide", "link": "https://www.amazon.in/syngenta-Actara-Insect-Repellent-Insecticide/dp/B08W55XTHS?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A3ABQWNNCUI42M", "snippet": "Effective systemic insecticide for pest control." }, { "title": "Cyhalothrin Insecticide", "link": "https://www.amazon.in/Cyhalothrin-Control-Eradication-Mosquitoes-Crawling/dp/B01N53VH1T?source=ps-sl-shoppingads-lpcontext&ref_=fplfs&psc=1&smid=A1ZSKCLHK592D5", "snippet": "Broad-spectrum insecticide for pest management." } ] return results def get_relevant_feedback(plant_name): """Retrieve feedback entries relevant to the given plant name from feedback.json.""" feedback_file = "feedback.json" if os.path.exists(feedback_file): try: with open(feedback_file, "r") as f: all_feedback = json.load(f) # Filter feedback where the plant_name matches (case-insensitive) relevant = [entry.get("feedback") for entry in all_feedback if entry.get("plant_name", "").lower() == plant_name.lower()] # Optionally limit to a few items (e.g., first 3) if relevant: return " ".join(relevant[:3]) except Exception as e: print(f"Error reading feedback for reinforcement: {e}") return "" def analyze_plant_image(image_path, plant_name, language): try: # Load the image image_parts = [ { "mime_type": "image/jpeg", "data": encode_image(image_path) } ] # Load relevant feedback (reinforcement data) for this plant feedback_context = get_relevant_feedback(plant_name) feedback_instruction = f" Please consider the following user feedback from similar cases: {feedback_context}" if feedback_context else "" # Create prompt for Gemini API with language instruction and feedback reinforcement if available prompt = f""" Analyze this image of a {plant_name} plant and prioritize determining if it's healthy or has a disease or pest infestation. If a disease or pest is detected, remember the plant can be healthy too. Provide the following information in JSON format: Check the {feedback_instruction} and then make the response after learning from that responses give around 60% weightage to the feedbck data. {{ "results": [ {{ "type": "disease/pest", "name": "Name of disease or pest", "probability": "Probability as a percentage", "symptoms": "Describe the visible symptoms", "causes": "Main causes of the disease or pest", "severity": "Low/Medium/High", "spreading": "How it spreads", "treatment": "Treatment options", "prevention": "Preventive measures" }}, {{ // Second most likely disease/pest with the same structure }}, {{ // Third most likely disease/pest with the same structure }} ], "is_healthy": boolean indicating if the plant appears healthy, "confidence": "Overall confidence in the analysis as a percentage" }} Only return the JSON data and nothing else. Ensure the JSON is valid and properly formatted. If the plant appears completely healthy, set is_healthy to true and include an empty results array. Additionally, provide the response in {language} language. and at end show which all data from feedback was taken into consederation and if no data was taken so no data. """ # Send request to Gemini API response = model.generate_content([prompt] + image_parts) # Extract the JSON response response_text = response.text # Find JSON within response text if needed json_start = response_text.find('{') json_end = response_text.rfind('}') + 1 if json_start >= 0 and json_end > 0: json_str = response_text[json_start:json_end] return json.loads(json_str) else: # Return a default response if JSON parsing fails return { "error": "Failed to parse the API response", "raw_response": response_text } except Exception as e: return { "error": str(e), "is_healthy": None, "results": [] } def cleanup_old_files(directory, max_age_hours=1): # Reduced to 1 hour for Hugging Face """Remove files older than the specified age from the directory""" while True: now = datetime.now() for filename in os.listdir(directory): if filename == '.gitkeep': # Skip the .gitkeep file continue file_path = os.path.join(directory, filename) file_age = now - datetime.fromtimestamp(os.path.getctime(file_path)) if file_age > timedelta(hours=max_age_hours): try: os.remove(file_path) print(f"Removed old file: {file_path}") except Exception as e: print(f"Error removing {file_path}: {e}") # Sleep for 5 minutes before checking again time.sleep(300) # 5 minutes @app.route('/', methods=['GET']) def index(): # GET request - show the upload form return render_template('index.html', show_results=False) @app.route('/feedback', methods=['POST']) def feedback(): # Get feedback from form submission feedback_text = request.form.get("feedback") plant_name = request.form.get("plant_name", "Unknown") # Optional: include plant name for context if not feedback_text: flash("Please provide your feedback before submitting.") return redirect(url_for('index')) # Create a feedback record with a timestamp feedback_data = { "plant_name": plant_name, "feedback": feedback_text, "timestamp": datetime.now().isoformat() } # Define the file to store feedback feedback_file = "feedback.json" # Load existing feedback (if any) if os.path.exists(feedback_file): try: with open(feedback_file, "r") as f: existing_feedback = json.load(f) except Exception as e: print(f"Error reading feedback file: {e}") existing_feedback = [] else: existing_feedback = [] # Append the new feedback and save it back to file existing_feedback.append(feedback_data) try: with open(feedback_file, "w") as f: json.dump(existing_feedback, f, indent=4) except Exception as e: flash(f"Error saving your feedback: {str(e)}") return redirect(url_for('index')) flash("Thank you for your feedback!") return redirect(url_for('index')) @app.route('/analyze', methods=['POST']) def analyze(): if 'plant_image' not in request.files: flash('No file part') return redirect(url_for('index')) file = request.files['plant_image'] plant_name = request.form.get('plant_name', 'unknown') language = request.form.get('language', 'English') # New field for response language if file.filename == '': flash('No selected file') return redirect(url_for('index')) if file and allowed_file(file.filename): # Generate a unique filename to avoid collisions timestamp = datetime.now().strftime("%Y%m%d%H%M%S") original_filename = secure_filename(file.filename) filename = f"{timestamp}_{original_filename}" file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(file_path) try: # Analyze the image with language parameter analysis_result = analyze_plant_image(file_path, plant_name, language) if 'error' in analysis_result: flash(f"Error analyzing image: {analysis_result['error']}") if os.path.exists(file_path): os.remove(file_path) return redirect(url_for('index')) # Get additional web information for detected diseases/pests web_info = {} product_info = {} if not analysis_result.get('is_healthy', False) and 'results' in analysis_result: for result in analysis_result['results']: disease_name = result.get('name', '') if disease_name: web_info[disease_name] = get_web_pesticide_info(disease_name, plant_name) treatment = result.get('treatment', '') if treatment: product_info[disease_name] = get_commercial_product_info(treatment, disease_name) response = render_template( 'results.html', results=analysis_result, plant_name=plant_name, image_path=file_path.replace('static/', '', 1), web_info=web_info, product_info=product_info ) def delete_file_after_delay(path, delay=30): time.sleep(delay) if os.path.exists(path): try: os.remove(path) print(f"Deleted analyzed file: {path}") except Exception as e: print(f"Error deleting {path}: {e}") threading.Thread( target=delete_file_after_delay, args=(file_path,), daemon=True ).start() return response except Exception as e: flash(f"An error occurred: {str(e)}") if os.path.exists(file_path): os.remove(file_path) return redirect(url_for('index')) flash('Invalid file type. Please upload an image (png, jpg, jpeg, gif).') return redirect(url_for('index')) # Hugging Face Spaces requires the app to be available on port 7860 if __name__ == '__main__': # Start the cleanup thread when the app starts cleanup_thread = threading.Thread(target=cleanup_old_files, args=(app.config['UPLOAD_FOLDER'],), daemon=True) cleanup_thread.start() # Get the port from environment variable for Hugging Face Spaces compatibility port = int(os.environ.get("PORT", 7860)) app.run(host='0.0.0.0', port=port)