api folder
This commit is contained in:
parent
797fec3135
commit
231b83afb5
4 changed files with 0 additions and 0 deletions
170
api/READMEPlantAPI.md
Normal file
170
api/READMEPlantAPI.md
Normal file
|
@ -0,0 +1,170 @@
|
|||
|
||||
🌱 Plant Health & Identification API
|
||||
|
||||
An intelligent backend service built for our hackathon project. This API uses a dual-model AI system to analyze an image of a plant, first identifying its species and then assessing its health.
|
||||
|
||||
🚀 The Problem
|
||||
|
||||
Have you ever wondered what kind of plant you have or why its leaves are suddenly turning yellow? Our project aims to provide a simple, accessible answer. This repository contains the backend API that powers our application, capable of providing a comprehensive plant analysis from a single image.
|
||||
|
||||
🧠 The AI Pipeline
|
||||
|
||||
This API uses a two-stage process to analyze an image:
|
||||
|
||||
Species Identification: We use a custom-trained TensorFlow/Keras model, built on a MobileNetV2 architecture. This model was fine-tuned on a dataset of six specific plant types to achieve high accuracy in identifying which plant is in the image.
|
||||
|
||||
Health Assessment: Once the plant's species is known (e.g., "tomato"), we use OpenAI's powerful, open-source CLIP model. We dynamically generate text prompts like "a photo of a healthy tomato plant" or "a photo of a sick tomato plant with yellow spots" and ask CLIP which description best matches the image. This zero-shot approach allows for a flexible and nuanced understanding of the plant's health.
|
||||
|
||||
✨ Features
|
||||
|
||||
Identify 6 Plant Species: Accurately distinguishes between tomato, basil, mint, lettuce, rosemary, and strawberry.
|
||||
|
||||
Assess 4 Health States: Classifies plants as Healthy, Diseased, Dehydrated, or Dead.
|
||||
|
||||
Confidence Scores: Provides confidence levels for both the species identification and the health assessment.
|
||||
|
||||
Simple JSON API: Easy to integrate with any frontend or mobile application.
|
||||
|
||||
🛠️ Tech Stack
|
||||
|
||||
Backend Framework: Flask
|
||||
|
||||
AI / Machine Learning: TensorFlow (Keras), PyTorch, OpenAI CLIP
|
||||
|
||||
Image Processing: Pillow, OpenCV
|
||||
|
||||
Core Language: Python 3.10
|
||||
|
||||
🔌 API Documentation
|
||||
|
||||
This is the documentation for the main analysis endpoint.
|
||||
|
||||
Analyze a Plant Image
|
||||
|
||||
Endpoint: /analyze
|
||||
|
||||
Method: POST
|
||||
|
||||
Body: multipart/form-data
|
||||
|
||||
The request must contain a file field named image.
|
||||
|
||||
Successful Response (Status 200 OK)
|
||||
|
||||
The API will return a JSON object with the analysis.
|
||||
|
||||
Example Response:
|
||||
|
||||
Generated json
|
||||
{
|
||||
"plant_species": "tomato",
|
||||
"identification_confidence": "97.45%",
|
||||
"health_status": "Healthy",
|
||||
"health_confidence": "89.12%",
|
||||
"health_breakdown": {
|
||||
"Healthy": 0.8912,
|
||||
"Diseased": 0.0562,
|
||||
"Dehydrated": 0.0421,
|
||||
"Dead": 0.0105
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Field Descriptions:
|
||||
|
||||
Key Type Description
|
||||
plant_species String The identified species of the plant.
|
||||
identification_confidence String The model's confidence in the species identification.
|
||||
health_status String The most likely health status of the plant.
|
||||
health_confidence String The model's confidence in the health assessment.
|
||||
health_breakdown Object A dictionary of raw probability scores for each health state.
|
||||
Error Responses
|
||||
|
||||
If the request is missing an image, the API will return:
|
||||
|
||||
Status: 400 Bad Request
|
||||
|
||||
Body: {"error": "No image file provided"}
|
||||
|
||||
For any other server-side issues, the API will return:
|
||||
|
||||
Status: 500 Internal Server Error
|
||||
|
||||
Body: {"error": "An internal server error occurred."}
|
||||
|
||||
🖥️ How to Run Locally
|
||||
|
||||
To run this backend server on your own machine, follow these steps.
|
||||
|
||||
Clone the Repository:
|
||||
|
||||
Generated bash
|
||||
git clone <repository-url>
|
||||
cd <repository-name>/backend
|
||||
IGNORE_WHEN_COPYING_START
|
||||
content_copy
|
||||
download
|
||||
Use code with caution.
|
||||
Bash
|
||||
IGNORE_WHEN_COPYING_END
|
||||
|
||||
Create a Virtual Environment:
|
||||
|
||||
Generated bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
IGNORE_WHEN_COPYING_START
|
||||
content_copy
|
||||
download
|
||||
Use code with caution.
|
||||
Bash
|
||||
IGNORE_WHEN_COPYING_END
|
||||
|
||||
Install Dependencies:
|
||||
This can take a while as it will download TensorFlow and PyTorch.
|
||||
|
||||
Generated bash
|
||||
pip install -r requirements.txt
|
||||
IGNORE_WHEN_COPYING_START
|
||||
content_copy
|
||||
download
|
||||
Use code with caution.
|
||||
Bash
|
||||
IGNORE_WHEN_COPYING_END
|
||||
|
||||
Place the Model:
|
||||
Make sure you have the trained Keras model (BestModel.keras) inside the models/ directory.
|
||||
|
||||
Run the Server:
|
||||
|
||||
Generated bash
|
||||
python app.py
|
||||
IGNORE_WHEN_COPYING_START
|
||||
content_copy
|
||||
download
|
||||
Use code with caution.
|
||||
Bash
|
||||
IGNORE_WHEN_COPYING_END
|
||||
|
||||
The API will now be running on your local machine at http://127.0.0.1:5000.
|
||||
|
||||
📁 Project Structure
|
||||
Generated code
|
||||
/backend
|
||||
|-- app.py # The main Flask server and API logic.
|
||||
|-- models/ # Folder for the trained Keras model.
|
||||
| |-- BestModel.keras
|
||||
|-- requirements.txt # Python dependencies.
|
||||
|-- .gitignore # Files to be ignored by Git (like the venv).
|
||||
IGNORE_WHEN_COPYING_START
|
||||
content_copy
|
||||
download
|
||||
Use code with caution.
|
||||
IGNORE_WHEN_COPYING_END
|
||||
👥 Authors
|
||||
|
||||
[Your Name]
|
||||
|
||||
[Teammate's Name]
|
||||
|
||||
[Teammate's Name]
|
115
api/abb.py
Normal file
115
api/abb.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
import io
|
||||
import traceback
|
||||
from flask import Flask, request, jsonify
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
import clip
|
||||
import tensorflow as tf
|
||||
|
||||
# --- Configuration ---
|
||||
class Config:
|
||||
"""Groups all required configuration variables in one place."""
|
||||
PLANT_MODEL_PATH = 'models/BestModel.keras'
|
||||
|
||||
PLANT_CLASSES = ['tomato', 'basil', 'mint', 'lettuce', 'rosemary', 'strawberry']
|
||||
IMG_SIZE = (384, 384)
|
||||
|
||||
# Health analysis prompt
|
||||
HEALTH_PROMPTS = [
|
||||
"a photo of a healthy {plant} plant with vibrant green leaves",
|
||||
"a photo of a sick {plant} plant with yellow spots or discoloration",
|
||||
"a photo of a dehydrated {plant} plant with wilted or drooping leaves",
|
||||
"a photo of a dead {plant} plant with brown, dry, or crispy leaves"
|
||||
]
|
||||
HEALTH_LABELS = ["Healthy", "Diseased", "Dehydrated", "Dead"]
|
||||
|
||||
# --- Application Setup ---
|
||||
app = Flask(__name__)
|
||||
|
||||
# --- Model Loading ---
|
||||
def load_models():
|
||||
"""Loads and initializes all required machine learning models."""
|
||||
# Load plant identification model
|
||||
print("1. Loading plant identification model...")
|
||||
plant_model = tf.keras.models.load_model(Config.PLANT_MODEL_PATH)
|
||||
|
||||
# Load model for health analysis
|
||||
print("2. Loading CLIP model for health analysis...")
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
clip_model, clip_preprocess = clip.load("ViT-B/32", device=device)
|
||||
|
||||
print("\nAll models loaded successfully.")
|
||||
return plant_model, clip_model, clip_preprocess, device
|
||||
|
||||
# Load models
|
||||
plant_model, clip_model, clip_preprocess, device = load_models()
|
||||
|
||||
# --- Core ML Functions ---
|
||||
def identify_plant(image):
|
||||
"""Plant identification"""
|
||||
img = image.resize(Config.IMG_SIZE)
|
||||
img_array = tf.keras.preprocessing.image.img_to_array(img)
|
||||
img_array = tf.expand_dims(img_array, 0)
|
||||
img_array = tf.keras.applications.mobilenet_v2.preprocess_input(img_array)
|
||||
|
||||
preds = plant_model.predict(img_array, verbose=0)
|
||||
best_idx = np.argmax(preds[0])
|
||||
|
||||
plant_name = Config.PLANT_CLASSES[best_idx]
|
||||
confidence = float(np.max(preds))
|
||||
|
||||
return plant_name, confidence
|
||||
|
||||
def assess_health(plant_name, image):
|
||||
"""Plant health"""
|
||||
prompts = [p.format(plant=plant_name) for p in Config.HEALTH_PROMPTS]
|
||||
image_input = clip_preprocess(image).unsqueeze(0).to(device)
|
||||
text_tokens = clip.tokenize(prompts).to(device)
|
||||
|
||||
with torch.no_grad():
|
||||
image_features = clip_model.encode_image(image_input)
|
||||
text_features = clip_model.encode_text(text_tokens)
|
||||
|
||||
image_features /= image_features.norm(dim=-1, keepdim=True)
|
||||
text_features /= text_features.norm(dim=-1, keepdim=True)
|
||||
|
||||
similarity = (100.0 * image_features @ text_features.T).softmax(dim=-1)
|
||||
probs = similarity.cpu().numpy()[0]
|
||||
|
||||
status = Config.HEALTH_LABELS[np.argmax(probs)]
|
||||
confidence = float(np.max(probs))
|
||||
probabilities = {label: float(p) for label, p in zip(Config.HEALTH_LABELS, probs)}
|
||||
|
||||
return status, confidence, probabilities
|
||||
|
||||
# --- API Endpoint ---
|
||||
@app.route('/analyze', methods=['POST'])
|
||||
def analyze_plant_image():
|
||||
"""Image analysis."""
|
||||
if 'image' not in request.files:
|
||||
return jsonify({'error': 'No image file provided'}), 400
|
||||
|
||||
try:
|
||||
file = request.files['image']
|
||||
image = Image.open(io.BytesIO(file.read())).convert("RGB")
|
||||
|
||||
plant_name, plant_conf = identify_plant(image.copy())
|
||||
health_status, health_conf, health_probs = assess_health(plant_name, image)
|
||||
|
||||
return jsonify({
|
||||
'plant_species': plant_name,
|
||||
'identification_confidence': f"{plant_conf:.2%}",
|
||||
'health_status': health_status,
|
||||
'health_confidence': f"{health_conf:.2%}",
|
||||
'health_breakdown': health_probs
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print("An error occurred:", str(e))
|
||||
traceback.print_exc()
|
||||
return jsonify({'error': 'An internal server error occurred.'}), 500
|
||||
|
||||
# --- Main Execution ---
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
BIN
api/models/BestModel.keras
Normal file
BIN
api/models/BestModel.keras
Normal file
Binary file not shown.
55
api/requirements.txt
Normal file
55
api/requirements.txt
Normal file
|
@ -0,0 +1,55 @@
|
|||
absl-py==2.3.1
|
||||
astunparse==1.6.3
|
||||
blinker==1.9.0
|
||||
certifi==2025.7.14
|
||||
charset-normalizer==3.4.2
|
||||
click==8.2.1
|
||||
-e git+https://github.com/openai/CLIP.git@dcba3cb2e2827b402d2701e7e1c7d9fed8a20ef1#egg=clip
|
||||
filelock==3.18.0
|
||||
Flask==3.1.1
|
||||
flatbuffers==25.2.10
|
||||
fsspec==2025.7.0
|
||||
ftfy==6.3.1
|
||||
gast==0.6.0
|
||||
google-pasta==0.2.0
|
||||
grpcio==1.74.0
|
||||
gunicorn==21.2.0
|
||||
h5py==3.14.0
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
keras==3.11.1
|
||||
libclang==18.1.1
|
||||
Markdown==3.8.2
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==3.0.2
|
||||
mdurl==0.1.2
|
||||
ml_dtypes==0.5.3
|
||||
mpmath==1.3.0
|
||||
namex==0.1.0
|
||||
networkx==3.4.2
|
||||
numpy==2.1.3
|
||||
opt_einsum==3.4.0
|
||||
optree==0.17.0
|
||||
packaging==25.0
|
||||
pillow==11.3.0
|
||||
protobuf==5.29.5
|
||||
Pygments==2.19.2
|
||||
regex==2025.7.34
|
||||
requests==2.32.4
|
||||
rich==14.1.0
|
||||
six==1.17.0
|
||||
sympy==1.14.0
|
||||
tensorboard==2.19.0
|
||||
tensorboard-data-server==0.7.2
|
||||
tensorflow==2.19.0
|
||||
tensorflow-io-gcs-filesystem==0.37.1
|
||||
termcolor==3.1.0
|
||||
torch==2.7.1
|
||||
torchvision==0.22.1
|
||||
tqdm==4.67.1
|
||||
typing_extensions==4.14.1
|
||||
urllib3==2.5.0
|
||||
wcwidth==0.2.13
|
||||
Werkzeug==3.1.3
|
||||
wrapt==1.17.2
|
Loading…
Add table
Add a link
Reference in a new issue