Update app.py
Browse files
app.py
CHANGED
|
@@ -629,6 +629,233 @@ def leaf_detection():
|
|
| 629 |
if 'con' in locals():
|
| 630 |
con.close()
|
| 631 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
# ============= DEFAULT ROUTE =============
|
| 633 |
|
| 634 |
@app.route("/", methods=["GET"])
|
|
|
|
| 629 |
if 'con' in locals():
|
| 630 |
con.close()
|
| 631 |
|
| 632 |
+
# ============= LEAF ANALYSIS (ALTERNATIVE ENDPOINT) =============
|
| 633 |
+
|
| 634 |
+
@app.route("/api/leaf-analysis", methods=["POST"])
|
| 635 |
+
@token_required
|
| 636 |
+
def leaf_analysis():
|
| 637 |
+
"""Leaf analysis endpoint (alternative to leaf-detection) - returns yes/no"""
|
| 638 |
+
try:
|
| 639 |
+
data = request.json
|
| 640 |
+
|
| 641 |
+
# Handle image input
|
| 642 |
+
image_data = None
|
| 643 |
+
if 'image_url' in data:
|
| 644 |
+
# Base64 image from frontend
|
| 645 |
+
image_url = data['image_url']
|
| 646 |
+
if ',' in image_url:
|
| 647 |
+
image_data = image_url.split(',')[1]
|
| 648 |
+
else:
|
| 649 |
+
image_data = image_url
|
| 650 |
+
elif 'image' in request.files:
|
| 651 |
+
image_file = request.files['image']
|
| 652 |
+
image = Image.open(image_file.stream)
|
| 653 |
+
buffered = io.BytesIO()
|
| 654 |
+
image.save(buffered, format="JPEG")
|
| 655 |
+
image_data = base64.b64encode(buffered.getvalue()).decode()
|
| 656 |
+
else:
|
| 657 |
+
return jsonify({"error": "No image provided"}), 400
|
| 658 |
+
|
| 659 |
+
# Get additional data from request
|
| 660 |
+
plant_type = data.get('plant', 'unknown')
|
| 661 |
+
|
| 662 |
+
# Generate random yes/no result
|
| 663 |
+
result = random.choice(["yes", "no"])
|
| 664 |
+
|
| 665 |
+
response_data = {
|
| 666 |
+
"result": result,
|
| 667 |
+
"disease_detected": result == "yes",
|
| 668 |
+
"message": "Disease detected in leaf" if result == "yes" else "Leaf appears healthy",
|
| 669 |
+
"confidence": random.randint(80, 98),
|
| 670 |
+
"plant": plant_type,
|
| 671 |
+
"disease_name": random.choice(["Leaf Spot", "Early Blight", "Late Blight", "Rust", "Powdery Mildew"]) if result == "yes" else "None",
|
| 672 |
+
"severity": random.choice(["mild", "moderate", "severe"]) if result == "yes" else "none",
|
| 673 |
+
"treatments": [
|
| 674 |
+
"Apply fungicide spray",
|
| 675 |
+
"Remove affected leaves",
|
| 676 |
+
"Improve air circulation",
|
| 677 |
+
"Reduce watering frequency"
|
| 678 |
+
] if result == "yes" else ["Continue regular care", "Monitor plant health"],
|
| 679 |
+
"prevention": [
|
| 680 |
+
"Regular plant inspection",
|
| 681 |
+
"Proper spacing between plants",
|
| 682 |
+
"Avoid overhead watering",
|
| 683 |
+
"Good garden hygiene"
|
| 684 |
+
]
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
# Save to database
|
| 688 |
+
try:
|
| 689 |
+
con = conn()
|
| 690 |
+
cur = con.cursor()
|
| 691 |
+
cur.execute("""
|
| 692 |
+
INSERT INTO leaf_analysis (user_id, image_data, plant_type, result,
|
| 693 |
+
disease_detected, analysis_data, created_at)
|
| 694 |
+
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
| 695 |
+
""", (request.user["user_id"], image_data, plant_type, result,
|
| 696 |
+
result == "yes", json.dumps(response_data), datetime.datetime.now()))
|
| 697 |
+
con.commit()
|
| 698 |
+
except Exception as db_error:
|
| 699 |
+
print(f"Database error in leaf analysis: {db_error}")
|
| 700 |
+
# Continue without saving to database
|
| 701 |
+
|
| 702 |
+
return jsonify(response_data), 200
|
| 703 |
+
|
| 704 |
+
except Exception as e:
|
| 705 |
+
print(f"Leaf analysis error: {e}")
|
| 706 |
+
return jsonify({"error": "Failed to analyze leaf image"}), 500
|
| 707 |
+
finally:
|
| 708 |
+
if 'cur' in locals():
|
| 709 |
+
cur.close()
|
| 710 |
+
if 'con' in locals():
|
| 711 |
+
con.close()
|
| 712 |
+
|
| 713 |
+
@app.route("/api/leaf-analysis/history", methods=["GET"])
|
| 714 |
+
@token_required
|
| 715 |
+
def get_leaf_analysis_history():
|
| 716 |
+
"""Get user's leaf analysis history"""
|
| 717 |
+
try:
|
| 718 |
+
con = conn()
|
| 719 |
+
cur = con.cursor(dictionary=True)
|
| 720 |
+
cur.execute("""
|
| 721 |
+
SELECT id, plant_type, result, disease_detected, analysis_data, created_at
|
| 722 |
+
FROM leaf_analysis
|
| 723 |
+
WHERE user_id = %s
|
| 724 |
+
ORDER BY created_at DESC
|
| 725 |
+
LIMIT 20
|
| 726 |
+
""", (request.user["user_id"],))
|
| 727 |
+
|
| 728 |
+
history = cur.fetchall()
|
| 729 |
+
|
| 730 |
+
# Parse JSON analysis results
|
| 731 |
+
for item in history:
|
| 732 |
+
if item['analysis_data']:
|
| 733 |
+
item['analysis_data'] = json.loads(item['analysis_data'])
|
| 734 |
+
|
| 735 |
+
return jsonify({"history": history}), 200
|
| 736 |
+
|
| 737 |
+
except Exception as e:
|
| 738 |
+
print(f"Leaf analysis history error: {e}")
|
| 739 |
+
return jsonify({"history": []}), 200
|
| 740 |
+
finally:
|
| 741 |
+
if 'cur' in locals():
|
| 742 |
+
cur.close()
|
| 743 |
+
if 'con' in locals():
|
| 744 |
+
con.close()
|
| 745 |
+
|
| 746 |
+
# ============= SOIL ANALYSIS (IMAGE + YES/NO) =============
|
| 747 |
+
|
| 748 |
+
@app.route("/api/soil-analysis", methods=["POST"])
|
| 749 |
+
@token_required
|
| 750 |
+
def soil_analysis():
|
| 751 |
+
"""Soil analysis with image input - returns yes/no for soil quality"""
|
| 752 |
+
try:
|
| 753 |
+
# Handle image input
|
| 754 |
+
image_data = None
|
| 755 |
+
if 'image' in request.files:
|
| 756 |
+
image_file = request.files['image']
|
| 757 |
+
image = Image.open(image_file.stream)
|
| 758 |
+
buffered = io.BytesIO()
|
| 759 |
+
image.save(buffered, format="JPEG")
|
| 760 |
+
image_data = base64.b64encode(buffered.getvalue()).decode()
|
| 761 |
+
elif request.json and 'image_base64' in request.json:
|
| 762 |
+
image_data = request.json['image_base64']
|
| 763 |
+
if ',' in image_data:
|
| 764 |
+
image_data = image_data.split(',')[1]
|
| 765 |
+
else:
|
| 766 |
+
return jsonify({"error": "No image provided"}), 400
|
| 767 |
+
|
| 768 |
+
# Generate random yes/no result for soil quality
|
| 769 |
+
result = random.choice(["yes", "no"])
|
| 770 |
+
|
| 771 |
+
response_data = {
|
| 772 |
+
"result": result,
|
| 773 |
+
"soil_quality": "good" if result == "yes" else "poor",
|
| 774 |
+
"message": "Soil appears healthy and fertile" if result == "yes" else "Soil needs improvement",
|
| 775 |
+
"confidence": random.randint(75, 95),
|
| 776 |
+
"soil_type": random.choice(["Loamy", "Clay", "Sandy", "Silty"]),
|
| 777 |
+
"ph_level": round(random.uniform(5.5, 8.0), 1),
|
| 778 |
+
"nutrients": {
|
| 779 |
+
"nitrogen": random.choice(["low", "medium", "high"]),
|
| 780 |
+
"phosphorus": random.choice(["low", "medium", "high"]),
|
| 781 |
+
"potassium": random.choice(["low", "medium", "high"])
|
| 782 |
+
},
|
| 783 |
+
"recommendations": [
|
| 784 |
+
"Add organic compost",
|
| 785 |
+
"Test pH levels regularly",
|
| 786 |
+
"Consider crop rotation"
|
| 787 |
+
] if result == "yes" else [
|
| 788 |
+
"Add organic matter",
|
| 789 |
+
"Improve drainage",
|
| 790 |
+
"Test for nutrient deficiencies",
|
| 791 |
+
"Consider soil amendments"
|
| 792 |
+
],
|
| 793 |
+
"suitable_crops": [
|
| 794 |
+
"Tomatoes", "Wheat", "Corn", "Beans"
|
| 795 |
+
] if result == "yes" else [
|
| 796 |
+
"Cover crops", "Legumes for nitrogen fixing"
|
| 797 |
+
]
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
# Save to database
|
| 801 |
+
try:
|
| 802 |
+
con = conn()
|
| 803 |
+
cur = con.cursor()
|
| 804 |
+
cur.execute("""
|
| 805 |
+
INSERT INTO soil_analysis (user_id, image_data, result, soil_quality,
|
| 806 |
+
analysis_data, created_at)
|
| 807 |
+
VALUES (%s, %s, %s, %s, %s, %s)
|
| 808 |
+
""", (request.user["user_id"], image_data, result, response_data["soil_quality"],
|
| 809 |
+
json.dumps(response_data), datetime.datetime.now()))
|
| 810 |
+
con.commit()
|
| 811 |
+
except Exception as db_error:
|
| 812 |
+
print(f"Database error in soil analysis: {db_error}")
|
| 813 |
+
# Continue without saving to database
|
| 814 |
+
|
| 815 |
+
return jsonify(response_data), 200
|
| 816 |
+
|
| 817 |
+
except Exception as e:
|
| 818 |
+
print(f"Soil analysis error: {e}")
|
| 819 |
+
return jsonify({"error": "Failed to analyze soil image"}), 500
|
| 820 |
+
finally:
|
| 821 |
+
if 'cur' in locals():
|
| 822 |
+
cur.close()
|
| 823 |
+
if 'con' in locals():
|
| 824 |
+
con.close()
|
| 825 |
+
|
| 826 |
+
@app.route("/api/soil-analysis/history", methods=["GET"])
|
| 827 |
+
@token_required
|
| 828 |
+
def get_soil_analysis_history():
|
| 829 |
+
"""Get user's soil analysis history"""
|
| 830 |
+
try:
|
| 831 |
+
con = conn()
|
| 832 |
+
cur = con.cursor(dictionary=True)
|
| 833 |
+
cur.execute("""
|
| 834 |
+
SELECT id, result, soil_quality, analysis_data, created_at
|
| 835 |
+
FROM soil_analysis
|
| 836 |
+
WHERE user_id = %s
|
| 837 |
+
ORDER BY created_at DESC
|
| 838 |
+
LIMIT 20
|
| 839 |
+
""", (request.user["user_id"],))
|
| 840 |
+
|
| 841 |
+
history = cur.fetchall()
|
| 842 |
+
|
| 843 |
+
# Parse JSON analysis results
|
| 844 |
+
for item in history:
|
| 845 |
+
if item['analysis_data']:
|
| 846 |
+
item['analysis_data'] = json.loads(item['analysis_data'])
|
| 847 |
+
|
| 848 |
+
return jsonify({"history": history}), 200
|
| 849 |
+
|
| 850 |
+
except Exception as e:
|
| 851 |
+
print(f"Soil history error: {e}")
|
| 852 |
+
return jsonify({"history": []}), 200
|
| 853 |
+
finally:
|
| 854 |
+
if 'cur' in locals():
|
| 855 |
+
cur.close()
|
| 856 |
+
if 'con' in locals():
|
| 857 |
+
con.close()
|
| 858 |
+
|
| 859 |
# ============= DEFAULT ROUTE =============
|
| 860 |
|
| 861 |
@app.route("/", methods=["GET"])
|