|
import os |
|
import numpy as np |
|
import tensorflow as tf |
|
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_from_directory, send_file |
|
from werkzeug.utils import secure_filename |
|
from PIL import Image |
|
import io |
|
import base64 |
|
from datetime import datetime |
|
import tempfile |
|
|
|
|
|
try: |
|
from reportlab.lib.pagesizes import letter, A4 |
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, Table, TableStyle |
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
|
from reportlab.lib.units import inch |
|
from reportlab.lib import colors |
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT |
|
REPORTLAB_AVAILABLE = True |
|
print("β
ReportLab library loaded successfully!") |
|
except ImportError as e: |
|
REPORTLAB_AVAILABLE = False |
|
print(f"β οΈ ReportLab not available: {e}") |
|
print("π PDF generation will use client-side fallback only") |
|
|
|
app = Flask(__name__) |
|
app.secret_key = 'your-secret-key-here' |
|
|
|
|
|
UPLOAD_FOLDER = 'static/uploads' |
|
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} |
|
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 |
|
|
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER |
|
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH |
|
|
|
|
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True) |
|
os.makedirs('static/css', exist_ok=True) |
|
os.makedirs('static/js', exist_ok=True) |
|
os.makedirs('templates', exist_ok=True) |
|
|
|
@app.route('/uploads/<filename>') |
|
def uploaded_file(filename): |
|
"""Serve uploaded files""" |
|
return send_from_directory(app.config['UPLOAD_FOLDER'], filename) |
|
|
|
|
|
MODEL_PATH = "models/1.h5" |
|
try: |
|
model = tf.keras.models.load_model(MODEL_PATH) |
|
print("β
Model loaded successfully!") |
|
MODEL_LOADED = True |
|
except Exception as e: |
|
print(f"β Error loading model: {e}") |
|
MODEL_LOADED = False |
|
model = None |
|
|
|
|
|
|
|
CLASS_NAMES = ["Potato___Early_blight", "Potato___Late_blight", "Potato___healthy"] |
|
CLASS_DISPLAY_NAMES = ["Early Blight", "Late Blight", "Healthy"] |
|
CLASS_DESCRIPTIONS = { |
|
"Potato___Early_blight": "A common fungal disease that causes dark spots on potato leaves. Treatment with copper-based fungicides is recommended.", |
|
"Potato___Late_blight": "A serious disease caused by Phytophthora infestans. Immediate action required - remove infected plants and apply appropriate fungicides.", |
|
"Potato___healthy": "The potato plant appears healthy with no signs of disease detected. Continue good agricultural practices." |
|
} |
|
|
|
|
|
IMAGE_SIZE = 256 |
|
|
|
def allowed_file(filename): |
|
"""Check if file extension is allowed""" |
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS |
|
|
|
def preprocess_image(image): |
|
"""Preprocess the uploaded image for prediction""" |
|
try: |
|
|
|
if image.mode != "RGB": |
|
image = image.convert("RGB") |
|
|
|
|
|
image = image.resize((IMAGE_SIZE, IMAGE_SIZE)) |
|
|
|
|
|
img_array = np.array(image) |
|
|
|
|
|
|
|
|
|
|
|
|
|
img_array = np.expand_dims(img_array, axis=0) |
|
|
|
|
|
print(f"Image shape: {img_array.shape}") |
|
print(f"Image pixel range: [{img_array.min():.2f}, {img_array.max():.2f}]") |
|
|
|
return img_array |
|
except Exception as e: |
|
print(f"Error preprocessing image: {e}") |
|
return None |
|
|
|
def predict_disease(image): |
|
"""Make prediction on the preprocessed image""" |
|
if not MODEL_LOADED or model is None: |
|
return {"error": "Model not loaded"} |
|
|
|
try: |
|
|
|
processed_image = preprocess_image(image) |
|
if processed_image is None: |
|
return {"error": "Failed to preprocess image"} |
|
|
|
|
|
predictions = model.predict(processed_image) |
|
predicted_class_index = np.argmax(predictions[0]) |
|
confidence = float(np.max(predictions[0])) |
|
|
|
|
|
print(f"Raw predictions: {predictions[0]}") |
|
print(f"Predicted class index: {predicted_class_index}") |
|
print(f"Confidence: {confidence:.4f}") |
|
|
|
|
|
predicted_class = CLASS_NAMES[predicted_class_index] |
|
predicted_display_name = CLASS_DISPLAY_NAMES[predicted_class_index] |
|
|
|
|
|
all_predictions = {} |
|
for i, (class_name, display_name) in enumerate(zip(CLASS_NAMES, CLASS_DISPLAY_NAMES)): |
|
all_predictions[display_name] = { |
|
'probability': round(float(predictions[0][i]) * 100, 2), |
|
'description': CLASS_DESCRIPTIONS[class_name] |
|
} |
|
|
|
return { |
|
"predicted_class": predicted_display_name, |
|
"confidence": round(confidence * 100, 2), |
|
"description": CLASS_DESCRIPTIONS[predicted_class], |
|
"all_predictions": all_predictions, |
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
} |
|
|
|
except Exception as e: |
|
print(f"Prediction error: {e}") |
|
return {"error": f"Prediction failed: {str(e)}"} |
|
|
|
def get_recommendations(disease_name, confidence): |
|
"""Get treatment recommendations based on prediction""" |
|
recommendations = { |
|
'Early Blight': [ |
|
"Remove affected leaves immediately and dispose properly", |
|
"Apply copper-based fungicide spray", |
|
"Improve air circulation around plants", |
|
"Avoid overhead watering", |
|
"Consider crop rotation for next season" |
|
], |
|
'Late Blight': [ |
|
"URGENT: Remove and destroy infected plants immediately", |
|
"Apply systemic fungicides (metalaxyl-based)", |
|
"Monitor weather conditions closely", |
|
"Increase plant spacing for better air circulation", |
|
"Harvest healthy tubers as soon as possible" |
|
], |
|
'Healthy': [ |
|
"Continue current care practices", |
|
"Maintain proper watering schedule", |
|
"Monitor plants regularly for early signs of disease", |
|
"Ensure good soil drainage", |
|
"Apply balanced fertilizer as needed" |
|
] |
|
} |
|
return recommendations.get(disease_name, ["Consult agricultural expert for specific advice"]) |
|
|
|
@app.route('/') |
|
def index(): |
|
"""Main page""" |
|
return render_template('index.html', model_loaded=MODEL_LOADED) |
|
|
|
@app.route('/test') |
|
def test_upload(): |
|
"""Simple upload test page""" |
|
return render_template('test_upload.html') |
|
|
|
@app.route('/debug') |
|
def debug_upload(): |
|
"""Debug upload test page""" |
|
return render_template('debug_upload.html') |
|
|
|
@app.route('/predict', methods=['POST']) |
|
def predict(): |
|
"""Handle image upload and prediction""" |
|
try: |
|
print(f"Received prediction request. Files: {list(request.files.keys())}") |
|
|
|
if 'file' not in request.files: |
|
print("No file in request") |
|
return jsonify({'error': 'No file uploaded'}), 400 |
|
|
|
file = request.files['file'] |
|
print(f"File received: {file.filename}, size: {file.content_length if hasattr(file, 'content_length') else 'unknown'}") |
|
|
|
if file.filename == '': |
|
print("Empty filename") |
|
return jsonify({'error': 'No file selected'}), 400 |
|
|
|
if not allowed_file(file.filename): |
|
print(f"Invalid file type: {file.filename}") |
|
return jsonify({'error': 'Invalid file type. Please upload PNG, JPG, or JPEG files.'}), 400 |
|
|
|
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) |
|
|
|
|
|
filename = secure_filename(file.filename) |
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
filename = f"{timestamp}_{filename}" |
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) |
|
|
|
print(f"Saving file to: {filepath}") |
|
file.save(filepath) |
|
print(f"File saved successfully") |
|
|
|
|
|
if not os.path.exists(filepath): |
|
print(f"File was not saved properly: {filepath}") |
|
return jsonify({'error': 'Failed to save uploaded file'}), 500 |
|
|
|
print(f"File size on disk: {os.path.getsize(filepath)} bytes") |
|
|
|
|
|
try: |
|
image = Image.open(filepath) |
|
print(f"Image opened successfully: {image.size}, mode: {image.mode}") |
|
except Exception as e: |
|
print(f"Failed to open image: {e}") |
|
return jsonify({'error': f'Invalid image file: {str(e)}'}), 400 |
|
|
|
result = predict_disease(image) |
|
print(f"Prediction result: {result}") |
|
|
|
if 'error' in result: |
|
return jsonify(result), 500 |
|
|
|
|
|
result['recommendations'] = get_recommendations(result['predicted_class'], result['confidence']) |
|
result['image_url'] = url_for('uploaded_file', filename=filename) |
|
|
|
print(f"Final result with image URL: {result['image_url']}") |
|
return jsonify(result) |
|
|
|
except Exception as e: |
|
print(f"Prediction error: {e}") |
|
import traceback |
|
traceback.print_exc() |
|
return jsonify({'error': f'Prediction failed: {str(e)}'}), 500 |
|
|
|
@app.route('/predict_camera', methods=['POST']) |
|
def predict_camera(): |
|
"""Handle camera image prediction""" |
|
try: |
|
data = request.get_json() |
|
|
|
if 'image' not in data: |
|
return jsonify({'error': 'No image data provided'}), 400 |
|
|
|
|
|
image_data = data['image'].split(',')[1] |
|
image_bytes = base64.b64decode(image_data) |
|
image = Image.open(io.BytesIO(image_bytes)) |
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
filename = f"camera_{timestamp}.png" |
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) |
|
image.save(filepath) |
|
|
|
|
|
result = predict_disease(image) |
|
|
|
if 'error' in result: |
|
return jsonify(result), 500 |
|
|
|
|
|
result['recommendations'] = get_recommendations(result['predicted_class'], result['confidence']) |
|
result['image_url'] = url_for('uploaded_file', filename=filename) |
|
|
|
return jsonify(result) |
|
|
|
except Exception as e: |
|
return jsonify({'error': f'Camera prediction failed: {str(e)}'}), 500 |
|
|
|
@app.route('/health') |
|
def health(): |
|
"""Health check endpoint""" |
|
upload_dir_exists = os.path.exists(app.config['UPLOAD_FOLDER']) |
|
upload_dir_writable = os.access(app.config['UPLOAD_FOLDER'], os.W_OK) if upload_dir_exists else False |
|
|
|
return jsonify({ |
|
'status': 'healthy', |
|
'model_loaded': MODEL_LOADED, |
|
'upload_dir_exists': upload_dir_exists, |
|
'upload_dir_writable': upload_dir_writable, |
|
'upload_path': app.config['UPLOAD_FOLDER'], |
|
'timestamp': datetime.now().isoformat() |
|
}) |
|
|
|
@app.route('/debug/upload-test') |
|
def debug_upload_test(): |
|
"""Debug endpoint to test upload directory""" |
|
try: |
|
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) |
|
|
|
|
|
test_file = os.path.join(app.config['UPLOAD_FOLDER'], 'test.txt') |
|
with open(test_file, 'w') as f: |
|
f.write('test') |
|
|
|
|
|
os.remove(test_file) |
|
|
|
return jsonify({ |
|
'status': 'success', |
|
'message': 'Upload directory is working correctly', |
|
'path': app.config['UPLOAD_FOLDER'] |
|
}) |
|
except Exception as e: |
|
return jsonify({ |
|
'status': 'error', |
|
'message': f'Upload directory test failed: {str(e)}', |
|
'path': app.config['UPLOAD_FOLDER'] |
|
}), 500 |
|
|
|
def test_model_predictions(): |
|
"""Test the model with some dummy data to verify it's working correctly""" |
|
if not MODEL_LOADED or model is None: |
|
return {"error": "Model not loaded"} |
|
|
|
try: |
|
|
|
dummy_image = np.random.randint(0, 255, (1, IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.uint8) |
|
|
|
|
|
predictions = model.predict(dummy_image) |
|
|
|
print(f"Model test - Input shape: {dummy_image.shape}") |
|
print(f"Model test - Output shape: {predictions.shape}") |
|
print(f"Model test - Predictions: {predictions[0]}") |
|
print(f"Model test - Sum of predictions: {np.sum(predictions[0])}") |
|
print(f"Model test - Class names order: {CLASS_NAMES}") |
|
|
|
return { |
|
"status": "success", |
|
"input_shape": str(dummy_image.shape), |
|
"output_shape": str(predictions.shape), |
|
"predictions": predictions[0].tolist(), |
|
"prediction_sum": float(np.sum(predictions[0])), |
|
"class_names": CLASS_NAMES |
|
} |
|
except Exception as e: |
|
print(f"Model test error: {e}") |
|
return {"error": f"Model test failed: {str(e)}"} |
|
|
|
@app.route('/debug/model-test') |
|
def debug_model_test(): |
|
"""Debug endpoint to test model functionality""" |
|
result = test_model_predictions() |
|
return jsonify(result) |
|
|
|
@app.errorhandler(413) |
|
def too_large(e): |
|
return jsonify({'error': 'File too large. Maximum size is 16MB.'}), 413 |
|
|
|
@app.errorhandler(404) |
|
def not_found(e): |
|
return render_template('index.html', model_loaded=MODEL_LOADED) |
|
|
|
def generate_pdf_report(prediction_data, image_path=None): |
|
"""Generate a professional PDF report for the disease prediction""" |
|
if not REPORTLAB_AVAILABLE: |
|
print("β ReportLab not available - cannot generate server-side PDF") |
|
return None |
|
|
|
try: |
|
|
|
temp_pdf = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') |
|
|
|
|
|
doc = SimpleDocTemplate(temp_pdf.name, pagesize=A4) |
|
styles = getSampleStyleSheet() |
|
story = [] |
|
|
|
|
|
title_style = ParagraphStyle( |
|
'CustomTitle', |
|
parent=styles['Title'], |
|
fontSize=24, |
|
spaceAfter=30, |
|
alignment=TA_CENTER, |
|
textColor=colors.darkgreen |
|
) |
|
|
|
heading_style = ParagraphStyle( |
|
'CustomHeading', |
|
parent=styles['Heading2'], |
|
fontSize=16, |
|
spaceAfter=12, |
|
textColor=colors.darkblue |
|
) |
|
|
|
|
|
story.append(Paragraph("π₯ POTATO DISEASE DETECTION REPORT", title_style)) |
|
story.append(Spacer(1, 20)) |
|
|
|
|
|
header_data = [ |
|
['Report Generated:', prediction_data.get('timestamp', datetime.now().strftime("%Y-%m-%d %H:%M:%S"))], |
|
['Analysis Method:', 'Deep Learning AI Classification'], |
|
['Model Version:', 'TensorFlow/Keras CNN v1.0'] |
|
] |
|
|
|
header_table = Table(header_data, colWidths=[2*inch, 4*inch]) |
|
header_table.setStyle(TableStyle([ |
|
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), |
|
('TEXTCOLOR', (0, 0), (-1, -1), colors.black), |
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'), |
|
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'), |
|
('FONTSIZE', (0, 0), (-1, -1), 10), |
|
('BOTTOMPADDING', (0, 0), (-1, -1), 12), |
|
('GRID', (0, 0), (-1, -1), 1, colors.black) |
|
])) |
|
|
|
story.append(header_table) |
|
story.append(Spacer(1, 30)) |
|
|
|
|
|
if image_path and os.path.exists(image_path): |
|
story.append(Paragraph("πΈ ANALYZED IMAGE", heading_style)) |
|
try: |
|
|
|
img = RLImage(image_path) |
|
img.drawHeight = 3 * inch |
|
img.drawWidth = 3 * inch |
|
story.append(img) |
|
story.append(Spacer(1, 20)) |
|
except Exception as img_error: |
|
print(f"Warning: Could not add image to PDF: {img_error}") |
|
story.append(Paragraph("Image could not be embedded in PDF", styles['Normal'])) |
|
story.append(Spacer(1, 20)) |
|
|
|
|
|
story.append(Paragraph("π― DIAGNOSIS RESULTS", heading_style)) |
|
|
|
|
|
diagnosis_data = [ |
|
['Predicted Disease:', prediction_data.get('predicted_class', 'Unknown')], |
|
['Confidence Level:', f"{prediction_data.get('confidence', 0):.2f}%"], |
|
['Risk Assessment:', get_risk_level(prediction_data.get('confidence', 0))] |
|
] |
|
|
|
diagnosis_table = Table(diagnosis_data, colWidths=[2*inch, 4*inch]) |
|
diagnosis_table.setStyle(TableStyle([ |
|
('BACKGROUND', (0, 0), (0, -1), colors.lightblue), |
|
('TEXTCOLOR', (0, 0), (-1, -1), colors.black), |
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'), |
|
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), |
|
('FONTSIZE', (0, 0), (-1, -1), 12), |
|
('BOTTOMPADDING', (0, 0), (-1, -1), 12), |
|
('GRID', (0, 0), (-1, -1), 1, colors.black) |
|
])) |
|
|
|
story.append(diagnosis_table) |
|
story.append(Spacer(1, 20)) |
|
|
|
|
|
story.append(Paragraph("π DESCRIPTION", heading_style)) |
|
description = Paragraph(prediction_data.get('description', 'No description available.'), styles['Normal']) |
|
story.append(description) |
|
story.append(Spacer(1, 20)) |
|
|
|
|
|
story.append(Paragraph("π PROBABILITY BREAKDOWN", heading_style)) |
|
|
|
prob_data = [['Disease Type', 'Probability']] |
|
all_predictions = prediction_data.get('all_predictions', {}) |
|
for disease, info in all_predictions.items(): |
|
prob_data.append([disease, f"{info.get('probability', 0):.2f}%"]) |
|
|
|
prob_table = Table(prob_data, colWidths=[3*inch, 2*inch]) |
|
prob_table.setStyle(TableStyle([ |
|
('BACKGROUND', (0, 0), (-1, 0), colors.grey), |
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), |
|
('ALIGN', (0, 0), (-1, -1), 'CENTER'), |
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), |
|
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), |
|
('FONTSIZE', (0, 0), (-1, -1), 10), |
|
('BOTTOMPADDING', (0, 0), (-1, -1), 12), |
|
('GRID', (0, 0), (-1, -1), 1, colors.black) |
|
])) |
|
|
|
story.append(prob_table) |
|
story.append(Spacer(1, 20)) |
|
|
|
|
|
story.append(Paragraph("π‘ TREATMENT RECOMMENDATIONS", heading_style)) |
|
|
|
recommendations = prediction_data.get('recommendations', []) |
|
for i, rec in enumerate(recommendations, 1): |
|
rec_text = f"{i}. {rec}" |
|
story.append(Paragraph(rec_text, styles['Normal'])) |
|
story.append(Spacer(1, 8)) |
|
|
|
story.append(Spacer(1, 30)) |
|
|
|
|
|
footer_style = ParagraphStyle( |
|
'Footer', |
|
parent=styles['Normal'], |
|
fontSize=10, |
|
alignment=TA_CENTER, |
|
textColor=colors.grey |
|
) |
|
|
|
story.append(Paragraph("_______________________________________________", footer_style)) |
|
story.append(Spacer(1, 10)) |
|
story.append(Paragraph("Generated by Potato Disease Detection System", footer_style)) |
|
story.append(Paragraph("Powered by Flask & TensorFlow | Lucky Sharma", footer_style)) |
|
story.append(Paragraph("Β© 2025 All Rights Reserved", footer_style)) |
|
|
|
|
|
doc.build(story) |
|
|
|
print(f"β
PDF report generated successfully: {temp_pdf.name}") |
|
return temp_pdf.name |
|
|
|
except Exception as e: |
|
print(f"β Error generating PDF: {e}") |
|
import traceback |
|
traceback.print_exc() |
|
return None |
|
|
|
def get_risk_level(confidence): |
|
"""Determine risk level based on confidence""" |
|
if confidence >= 80: |
|
return "High Confidence" |
|
elif confidence >= 60: |
|
return "Medium Confidence" |
|
else: |
|
return "Low Confidence - Manual Verification Recommended" |
|
|
|
@app.route('/generate-pdf-report', methods=['POST']) |
|
def generate_pdf_report_route(): |
|
"""Generate and download PDF report""" |
|
try: |
|
data = request.get_json() |
|
|
|
if not data: |
|
return jsonify({'error': 'No data provided'}), 400 |
|
|
|
|
|
if not REPORTLAB_AVAILABLE: |
|
print("β οΈ ReportLab not available, suggesting client-side fallback") |
|
return jsonify({ |
|
'error': 'Server-side PDF generation not available', |
|
'fallback': 'client', |
|
'message': 'ReportLab library not installed. Using client-side fallback.' |
|
}), 503 |
|
|
|
|
|
image_path = None |
|
if 'image_url' in data: |
|
|
|
image_filename = data['image_url'].split('/')[-1] |
|
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image_filename) |
|
|
|
|
|
if not os.path.exists(image_path): |
|
image_path = None |
|
|
|
|
|
pdf_path = generate_pdf_report(data, image_path) |
|
|
|
if not pdf_path: |
|
print("β PDF generation failed, suggesting client-side fallback") |
|
return jsonify({ |
|
'error': 'Server-side PDF generation failed', |
|
'fallback': 'client', |
|
'message': 'Could not generate PDF on server. Using client-side fallback.' |
|
}), 503 |
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
disease_name = data.get('predicted_class', 'unknown').replace(' ', '_') |
|
pdf_filename = f"potato_disease_report_{disease_name}_{timestamp}.pdf" |
|
|
|
print(f"β
PDF generated successfully: {pdf_filename}") |
|
return send_file( |
|
pdf_path, |
|
as_attachment=True, |
|
download_name=pdf_filename, |
|
mimetype='application/pdf' |
|
) |
|
|
|
except Exception as e: |
|
print(f"β PDF generation error: {e}") |
|
import traceback |
|
traceback.print_exc() |
|
return jsonify({ |
|
'error': f'PDF generation failed: {str(e)}', |
|
'fallback': 'client', |
|
'message': 'Server error occurred. Using client-side fallback.' |
|
}), 503 |
|
|
|
if __name__ == '__main__': |
|
print("π Starting Potato Disease Detection Flask App...") |
|
print(f"π Upload folder: {UPLOAD_FOLDER}") |
|
print(f"π€ Model loaded: {MODEL_LOADED}") |
|
print("π Access the app at: http://localhost:5000") |
|
print("π‘ Press Ctrl+C to stop the server") |
|
|
|
app.run(debug=True, host='0.0.0.0', port=5000) |
|
|