|
|
""" |
|
|
Gradio web app for Crop Recommendation System (compatible with gradio==3.50.0) |
|
|
|
|
|
- Avoids using Gradio args that differ across versions. |
|
|
- If model.pkl or model_meta.json don't exist, creates a small dummy sklearn pipeline |
|
|
and saves it so the Space will start and you can test the UI. |
|
|
""" |
|
|
|
|
|
import json |
|
|
import joblib |
|
|
import numpy as np |
|
|
import pandas as pd |
|
|
import traceback |
|
|
from pathlib import Path |
|
|
|
|
|
|
|
|
from sklearn.ensemble import RandomForestClassifier |
|
|
from sklearn.pipeline import Pipeline |
|
|
from sklearn.preprocessing import StandardScaler |
|
|
from sklearn.model_selection import train_test_split |
|
|
|
|
|
import gradio as gr |
|
|
|
|
|
MODEL_PATH = Path("model.pkl") |
|
|
META_PATH = Path("model_meta.json") |
|
|
|
|
|
|
|
|
_DEFAULT_LABELS = ["rice", "maize", "chickpea", "cotton", "wheat"] |
|
|
_DEFAULT_NUMERIC_COLS = ["N", "P", "K", "temperature", "humidity", "ph", "rainfall"] |
|
|
|
|
|
def build_and_save_dummy_model(model_path: Path, meta_path: Path): |
|
|
""" |
|
|
Build a tiny RandomForest pipeline trained on synthetic data and save to disk. |
|
|
This ensures the Space starts even without a user model. |
|
|
""" |
|
|
|
|
|
rng = np.random.RandomState(42) |
|
|
n_samples = 500 |
|
|
|
|
|
|
|
|
X = pd.DataFrame({ |
|
|
"N": rng.randint(0, 141, size=n_samples), |
|
|
"P": rng.randint(5, 146, size=n_samples), |
|
|
"K": rng.randint(5, 206, size=n_samples), |
|
|
"temperature": rng.uniform(8, 44, size=n_samples), |
|
|
"humidity": rng.randint(14, 101, size=n_samples), |
|
|
"ph": rng.uniform(3.5, 10, size=n_samples), |
|
|
"rainfall": rng.uniform(20, 300, size=n_samples) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
y = [] |
|
|
for i, row in X.iterrows(): |
|
|
if row["rainfall"] > 1500: |
|
|
y.append(0) |
|
|
elif row["temperature"] > 30 and row["rainfall"] < 800: |
|
|
y.append(3) |
|
|
elif row["ph"] < 5.5: |
|
|
y.append(2) |
|
|
elif row["N"] > 80: |
|
|
y.append(1) |
|
|
else: |
|
|
y.append(4) |
|
|
|
|
|
y = np.array(y) |
|
|
|
|
|
|
|
|
pipeline = Pipeline([ |
|
|
("scaler", StandardScaler()), |
|
|
("clf", RandomForestClassifier(n_estimators=50, random_state=42)) |
|
|
]) |
|
|
|
|
|
pipeline.fit(X, y) |
|
|
|
|
|
|
|
|
joblib.dump(pipeline, model_path) |
|
|
meta = { |
|
|
"numeric_cols": _DEFAULT_NUMERIC_COLS, |
|
|
|
|
|
"label_classes": _DEFAULT_LABELS |
|
|
} |
|
|
with open(meta_path, "w", encoding="utf-8") as f: |
|
|
json.dump(meta, f, indent=2) |
|
|
|
|
|
return pipeline, meta |
|
|
|
|
|
def load_model_and_meta(): |
|
|
""" |
|
|
Try to load model and meta from disk. If not present, build a dummy model and save it. |
|
|
Returns (model, meta, load_error_message_or_None) |
|
|
""" |
|
|
load_error = None |
|
|
try: |
|
|
if not MODEL_PATH.exists() or not META_PATH.exists(): |
|
|
|
|
|
model, meta = build_and_save_dummy_model(MODEL_PATH, META_PATH) |
|
|
return model, meta, None |
|
|
|
|
|
model = joblib.load(MODEL_PATH) |
|
|
with open(META_PATH, "r", encoding="utf-8") as f: |
|
|
meta = json.load(f) |
|
|
|
|
|
|
|
|
if "numeric_cols" not in meta or "label_classes" not in meta: |
|
|
raise KeyError("model_meta.json must contain 'numeric_cols' and 'label_classes' keys.") |
|
|
|
|
|
return model, meta, None |
|
|
except Exception as e: |
|
|
load_error = f"{type(e).__name__}: {e}\n\n{traceback.format_exc()}" |
|
|
|
|
|
fallback_model, fallback_meta = build_and_save_dummy_model(MODEL_PATH, META_PATH) |
|
|
return fallback_model, fallback_meta, load_error |
|
|
|
|
|
|
|
|
_model, _meta, _load_error = load_model_and_meta() |
|
|
|
|
|
|
|
|
label_classes = _meta.get("label_classes", _DEFAULT_LABELS) |
|
|
if isinstance(label_classes, dict): |
|
|
|
|
|
try: |
|
|
|
|
|
items = sorted(label_classes.items(), key=lambda kv: int(kv[0]) if str(kv[0]).isdigit() else kv[0]) |
|
|
label_list = [v for k, v in items] |
|
|
except Exception: |
|
|
|
|
|
label_list = list(label_classes.values()) |
|
|
label_classes = label_list |
|
|
elif isinstance(label_classes, list): |
|
|
label_classes = label_classes |
|
|
else: |
|
|
|
|
|
label_classes = [str(x) for x in label_classes] |
|
|
|
|
|
numeric_cols = _meta.get("numeric_cols", _DEFAULT_NUMERIC_COLS) |
|
|
|
|
|
def predict_crop(N, P, K, temperature, humidity, ph, rainfall): |
|
|
""" |
|
|
Predict crop recommendation and top-3 with confidences. |
|
|
|
|
|
Returns: (recommended_crop_str, confidence_str, top3_dict) |
|
|
""" |
|
|
if _load_error: |
|
|
|
|
|
return "Model load warning", "N/A", {"warning": _load_error} |
|
|
|
|
|
try: |
|
|
input_df = pd.DataFrame({ |
|
|
"N": [N], |
|
|
"P": [P], |
|
|
"K": [K], |
|
|
"temperature": [temperature], |
|
|
"humidity": [humidity], |
|
|
"ph": [ph], |
|
|
"rainfall": [rainfall] |
|
|
}) |
|
|
|
|
|
|
|
|
pred_enc = _model.predict(input_df)[0] |
|
|
|
|
|
|
|
|
if hasattr(_model, "predict_proba"): |
|
|
probs = _model.predict_proba(input_df)[0] |
|
|
else: |
|
|
|
|
|
n_labels = len(label_classes) |
|
|
probs = np.zeros(n_labels) |
|
|
probs[pred_enc] = 1.0 |
|
|
|
|
|
|
|
|
try: |
|
|
recommended_crop = label_classes[int(pred_enc)] |
|
|
except Exception: |
|
|
|
|
|
if str(pred_enc) in label_classes: |
|
|
recommended_crop = str(pred_enc) |
|
|
else: |
|
|
|
|
|
recommended_crop = str(pred_enc) |
|
|
|
|
|
|
|
|
try: |
|
|
confidence = probs[int(pred_enc)] |
|
|
except Exception: |
|
|
confidence = float(np.max(probs)) if len(probs) > 0 else 0.0 |
|
|
|
|
|
|
|
|
top_idx = np.argsort(probs)[::-1][:3] |
|
|
top3 = {} |
|
|
for rank, idx in enumerate(top_idx, 1): |
|
|
label = label_classes[idx] if idx < len(label_classes) else f"label_{idx}" |
|
|
top3[f"{rank}. {label}"] = f"{probs[idx]:.2%}" |
|
|
|
|
|
return recommended_crop, f"{confidence:.2%}", top3 |
|
|
|
|
|
except Exception as e: |
|
|
err = f"{type(e).__name__}: {e}\n\n{traceback.format_exc()}" |
|
|
return "Prediction failed", "N/A", {"error": err} |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Crop Recommendation System") as demo: |
|
|
gr.Markdown("# ๐พ Crop Recommendation System") |
|
|
gr.Markdown("Enter soil and weather parameters to get an AI-powered crop recommendation with confidence scores.") |
|
|
|
|
|
if _load_error: |
|
|
gr.Markdown("**โ ๏ธ Warning: There was an issue loading your provided model. A fallback/dummy model is in use.**") |
|
|
|
|
|
truncated = _load_error if len(_load_error) < 3000 else _load_error[:3000] + "\n\n...[truncated]" |
|
|
gr.Code(truncated, language="text") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### Soil Parameters") |
|
|
N = gr.Slider(label="Nitrogen (N)", minimum=0, maximum=140, value=90, step=1) |
|
|
P = gr.Slider(label="Phosphorus (P)", minimum=5, maximum=145, value=42, step=1) |
|
|
K = gr.Slider(label="Potassium (K)", minimum=5, maximum=205, value=43, step=1) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### Weather Parameters") |
|
|
temperature = gr.Slider(label="Temperature (ยฐC)", minimum=8, maximum=44, value=21, step=0.1) |
|
|
humidity = gr.Slider(label="Humidity (%)", minimum=14, maximum=100, value=82, step=1) |
|
|
ph = gr.Slider(label="Soil pH", minimum=3.5, maximum=10, value=6.5, step=0.1) |
|
|
rainfall = gr.Slider(label="Annual Rainfall (mm)", minimum=20, maximum=3000, value=203, step=10) |
|
|
|
|
|
predict_btn = gr.Button("๐ Get Crop Recommendation") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=2): |
|
|
recommended = gr.Textbox(label="๐พ Recommended Crop", interactive=False) |
|
|
confidence = gr.Textbox(label="โ
Confidence", interactive=False) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
|
|
|
top_3 = gr.JSON(label="๐ Top 3 Recommendations") |
|
|
|
|
|
predict_btn.click( |
|
|
fn=predict_crop, |
|
|
inputs=[N, P, K, temperature, humidity, ph, rainfall], |
|
|
outputs=[recommended, confidence, top_3] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
--- |
|
|
### Parameter Ranges (based on training data) |
|
|
- **Nitrogen (N)**: 0-140 kg/ha |
|
|
- **Phosphorus (P)**: 5-145 kg/ha |
|
|
- **Potassium (K)**: 5-205 kg/ha |
|
|
- **Temperature**: 8-44ยฐC |
|
|
- **Humidity**: 14-100% |
|
|
- **pH**: 3.5-10 |
|
|
- **Rainfall**: 20-3000 mm/year |
|
|
""") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
demo.launch(share=False) |
|
|
|