Spaces:
Sleeping
Sleeping
Commit ·
3bab7f4
1
Parent(s): 11672f5
style: change language to Indonesian
Browse files- src/streamlit_app.py +51 -58
src/streamlit_app.py
CHANGED
|
@@ -4,33 +4,37 @@ from PIL import Image
|
|
| 4 |
import numpy as np
|
| 5 |
import os
|
| 6 |
|
| 7 |
-
# --- CONFIGURATION ---
|
| 8 |
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
MODEL_PATH = os.path.join(SCRIPT_DIR, "best.pt")
|
| 10 |
|
| 11 |
-
# --- KNOWLEDGE BASE ---
|
| 12 |
SYMPTOM_QUESTIONS = {
|
| 13 |
"G1": "Daun berwana putih kecoklatan memanjang seperti mengering pada bagian tepi daun?",
|
| 14 |
-
"G2": "Pada pagi hari, ditemukan cairan bakteri
|
| 15 |
-
"G3": "
|
| 16 |
-
"G4": "
|
| 17 |
-
"G5": "
|
| 18 |
-
"G6": "
|
| 19 |
-
"G7": "
|
| 20 |
"G8": "Infeksi pada malai/leher berwarna abu-abu?",
|
| 21 |
-
"
|
| 22 |
-
"
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
YOLO_PROMPTS = {
|
| 29 |
-
"bacterial_leaf_blight": ["G1", "G2"],
|
| 30 |
-
"bacterial_leaf_streak": ["
|
| 31 |
"blast": ["G6", "G7", "G8"],
|
| 32 |
-
"tungro": ["
|
| 33 |
-
"brown_spot": [],
|
| 34 |
"downy_mildew": [],
|
| 35 |
"dead_heart": [],
|
| 36 |
"hispa": [],
|
|
@@ -38,15 +42,17 @@ YOLO_PROMPTS = {
|
|
| 38 |
}
|
| 39 |
|
| 40 |
PRODUCTION_RULES = [
|
| 41 |
-
{"disease": "Bacterial Leaf Blight (Hawar Daun Bakteri)", "symptoms": ["G1", "G2"], "source": "Literatur 1"},
|
| 42 |
-
{"disease": "
|
| 43 |
-
{"disease": "
|
| 44 |
-
{"disease": "
|
| 45 |
-
|
| 46 |
-
{"disease": "
|
|
|
|
|
|
|
|
|
|
| 47 |
]
|
| 48 |
|
| 49 |
-
# --- MODEL LOADING (Cached so it doesn't reload every click) ---
|
| 50 |
@st.cache_resource
|
| 51 |
def load_model():
|
| 52 |
try:
|
|
@@ -57,14 +63,12 @@ def load_model():
|
|
| 57 |
|
| 58 |
model = load_model()
|
| 59 |
|
| 60 |
-
|
| 61 |
-
st.title("🌾 Rice Doctor: Hybrid AI System")
|
| 62 |
st.markdown("""
|
| 63 |
-
1. **
|
| 64 |
-
2. **
|
| 65 |
""")
|
| 66 |
|
| 67 |
-
# Initialize Session State to keep data across re-runs
|
| 68 |
if 'prediction' not in st.session_state:
|
| 69 |
st.session_state['prediction'] = None
|
| 70 |
if 'priority_codes' not in st.session_state:
|
|
@@ -72,77 +76,66 @@ if 'priority_codes' not in st.session_state:
|
|
| 72 |
if 'analyzed' not in st.session_state:
|
| 73 |
st.session_state['analyzed'] = False
|
| 74 |
|
| 75 |
-
|
| 76 |
-
uploaded_file = st.file_uploader("Step 1: Upload Leaf Image", type=["jpg", "png", "jpeg"])
|
| 77 |
|
| 78 |
if uploaded_file is not None:
|
| 79 |
-
# Display image
|
| 80 |
image = Image.open(uploaded_file)
|
| 81 |
-
st.image(image, caption="
|
| 82 |
|
| 83 |
-
if st.button("
|
| 84 |
if model:
|
| 85 |
-
with st.spinner("
|
| 86 |
-
# Run Inference
|
| 87 |
results = model.predict(image, imgsz=640)
|
| 88 |
raw_name = results[0].names[results[0].probs.top1]
|
| 89 |
top_class_name = raw_name.lower().strip()
|
| 90 |
|
| 91 |
-
# Save to session state
|
| 92 |
st.session_state['prediction'] = top_class_name
|
| 93 |
st.session_state['priority_codes'] = YOLO_PROMPTS.get(top_class_name, [])
|
| 94 |
st.session_state['analyzed'] = True
|
| 95 |
|
| 96 |
-
# Force rerun to show step 2
|
| 97 |
st.rerun()
|
| 98 |
|
| 99 |
-
# --- STEP 2: DYNAMIC QUESTIONS ---
|
| 100 |
if st.session_state['analyzed']:
|
| 101 |
st.divider()
|
| 102 |
-
st.subheader("
|
| 103 |
|
| 104 |
pred = st.session_state['prediction']
|
| 105 |
codes = st.session_state['priority_codes']
|
| 106 |
|
| 107 |
-
st.info(f"🔍 **
|
| 108 |
|
| 109 |
if not codes:
|
| 110 |
-
st.warning("
|
| 111 |
else:
|
| 112 |
-
st.write("
|
| 113 |
|
| 114 |
-
# Dynamic Yes/No Questions
|
| 115 |
user_answers = {}
|
| 116 |
for code in codes:
|
| 117 |
question = SYMPTOM_QUESTIONS.get(code, "Unknown")
|
| 118 |
-
# Unique key is important in Streamlit
|
| 119 |
ans = st.radio(
|
| 120 |
f"**({code})** {question}",
|
| 121 |
-
options=["
|
| 122 |
-
index=0,
|
| 123 |
key=f"q_{code}"
|
| 124 |
)
|
| 125 |
-
if ans == "
|
| 126 |
user_answers[code] = True
|
| 127 |
|
| 128 |
st.divider()
|
| 129 |
-
st.markdown("**
|
| 130 |
|
| 131 |
-
# Full Checklist
|
| 132 |
all_symptoms_list = [f"{k}: {v}" for k,v in SYMPTOM_QUESTIONS.items()]
|
| 133 |
-
other_checks = st.multiselect("
|
| 134 |
|
| 135 |
-
# Process "Other" inputs
|
| 136 |
for item in other_checks:
|
| 137 |
code = item.split(":")[0].strip()
|
| 138 |
user_answers[code] = True
|
| 139 |
|
| 140 |
-
|
| 141 |
-
if st.button("Step 3: Run Forward Chaining", type="primary"):
|
| 142 |
user_facts = set(user_answers.keys())
|
| 143 |
|
| 144 |
if not user_facts:
|
| 145 |
-
st.error("
|
| 146 |
else:
|
| 147 |
matches = []
|
| 148 |
for rule in PRODUCTION_RULES:
|
|
@@ -151,7 +144,7 @@ if st.session_state['analyzed']:
|
|
| 151 |
matches.append(rule)
|
| 152 |
|
| 153 |
if matches:
|
| 154 |
-
st.success("
|
| 155 |
for m in matches:
|
| 156 |
st.markdown(f"""
|
| 157 |
### {m['disease']}
|
|
@@ -159,5 +152,5 @@ if st.session_state['analyzed']:
|
|
| 159 |
- **Logic:** Found all required symptoms {m['symptoms']}
|
| 160 |
""")
|
| 161 |
else:
|
| 162 |
-
st.error("
|
| 163 |
-
st.write("
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
import os
|
| 6 |
|
|
|
|
| 7 |
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
MODEL_PATH = os.path.join(SCRIPT_DIR, "best.pt")
|
| 9 |
|
|
|
|
| 10 |
SYMPTOM_QUESTIONS = {
|
| 11 |
"G1": "Daun berwana putih kecoklatan memanjang seperti mengering pada bagian tepi daun?",
|
| 12 |
+
"G2": "Pada pagi hari, dapat ditemukan cairan bakteri terlihat seperti butiran air pada bagian terinfeksi",
|
| 13 |
+
"G3": "Gejala terlihat seperti anak panah terdapat di antara urat daun?",
|
| 14 |
+
"G4": "Daun Berwarna kuning kecoklatan?",
|
| 15 |
+
"G5": "Luka pada daun terlihat transparan bila dihadapkan cahaya matahari?",
|
| 16 |
+
"G6": "Daun berbentuk belah ketupat?",
|
| 17 |
+
"G7": "Pada daun bagian tengah berwarna abu-abu atau berwarna putih dengan bagian tepi berwarna kecoklatan?",
|
| 18 |
"G8": "Infeksi pada malai/leher berwarna abu-abu?",
|
| 19 |
+
"G9": "Tanaman kerdil dan Gejala pada daun terjadi perubahan warna dari hijau menjadi jingga atau kemerahan?",
|
| 20 |
+
"G10": "Daun menguning, menggulung, mengering dan menjadi layu?",
|
| 21 |
+
"G11": "Bibit menjadi layu (kresek) tapi sulit dicabut?",
|
| 22 |
+
"G12": "Warna luka bercak menjadi jingga kekuningan dari ujung daun ke pangkal daun?",
|
| 23 |
+
"G13": "Ada bulatan kecil berwarna kuning pada pelepah daun?",
|
| 24 |
+
"G14": "Bercak cokelat pada daun, pelepah, dan bulir gabah?",
|
| 25 |
+
"G15": "Bercak daun sering menutupi permukaan daun, menyebabkan daun menjadi layu?",
|
| 26 |
+
"G16": "Bercak cokelat pada batang/leher malai?",
|
| 27 |
+
"G17": "Terdapat bercak berbentuk oval berwarna abu-abu kehijauan pada pelepah (tulang daun)?",
|
| 28 |
+
"G18": "Batang tanaman padi menjadi rapuh dan mudah rebah/jatuh?",
|
| 29 |
+
"G19": "Bibit yang sakit menjadi layu dan akhirnya mati?"
|
| 30 |
}
|
| 31 |
|
| 32 |
YOLO_PROMPTS = {
|
| 33 |
+
"bacterial_leaf_blight": ["G1", "G2", "G3"],
|
| 34 |
+
"bacterial_leaf_streak": ["G4", "G5"],
|
| 35 |
"blast": ["G6", "G7", "G8"],
|
| 36 |
+
"tungro": ["G9"],
|
| 37 |
+
"brown_spot": ["G14", "G15", "G16"],
|
| 38 |
"downy_mildew": [],
|
| 39 |
"dead_heart": [],
|
| 40 |
"hispa": [],
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
PRODUCTION_RULES = [
|
| 45 |
+
{"disease": "Bacterial Leaf Blight (Hawar Daun Bakteri)", "symptoms": ["G1", "G2", "G3"], "source": "Literatur 1"},
|
| 46 |
+
{"disease": "Bakteri Daun Bergaris (Bacterial Leaf Streak)", "symptoms": ["G4", "G5"], "source": "Literatur 1"},
|
| 47 |
+
{"disease": "Blas (Blast)", "symptoms": ["G6", "G7", "G8"], "source": "Literatur 1"},
|
| 48 |
+
{"disease": "Tungro", "symptoms": ["G9"], "source": "Literatur 1"},
|
| 49 |
+
|
| 50 |
+
{"disease": "Hawar Daun Bakteri (Bacterial Leaf Blight)", "symptoms": ["G10", "G11", "G12", "G13"], "source": "Literatur 2"},
|
| 51 |
+
|
| 52 |
+
{"disease": "Bercak Daun Coklat (Brown Spot)", "symptoms": ["G14", "G15", "G16"], "source": "Literatur 3"},
|
| 53 |
+
{"disease": "Hawar Pelepah (Sheath Blight)", "symptoms": ["G17", "G18", "G19"], "source": "Literatur 3"},
|
| 54 |
]
|
| 55 |
|
|
|
|
| 56 |
@st.cache_resource
|
| 57 |
def load_model():
|
| 58 |
try:
|
|
|
|
| 63 |
|
| 64 |
model = load_model()
|
| 65 |
|
| 66 |
+
st.title("🌾 Rice Doctor: Sistem Pakar Penyakit Padi")
|
|
|
|
| 67 |
st.markdown("""
|
| 68 |
+
1. **Inferensi AI**: Mendeteksi penyakit dari gambar.
|
| 69 |
+
2. **Sistem Pakar**: Memverifikasi diagnosis menggunakan logika Forward Chaining.
|
| 70 |
""")
|
| 71 |
|
|
|
|
| 72 |
if 'prediction' not in st.session_state:
|
| 73 |
st.session_state['prediction'] = None
|
| 74 |
if 'priority_codes' not in st.session_state:
|
|
|
|
| 76 |
if 'analyzed' not in st.session_state:
|
| 77 |
st.session_state['analyzed'] = False
|
| 78 |
|
| 79 |
+
uploaded_file = st.file_uploader("Langkah 1: Unggah Gambar Daun", type=["jpg", "png", "jpeg"])
|
|
|
|
| 80 |
|
| 81 |
if uploaded_file is not None:
|
|
|
|
| 82 |
image = Image.open(uploaded_file)
|
| 83 |
+
st.image(image, caption="Gambar yang Diunggah", use_container_width=True)
|
| 84 |
|
| 85 |
+
if st.button("Analisis Penyakit", type="primary"):
|
| 86 |
if model:
|
| 87 |
+
with st.spinner("Menganalisis..."):
|
|
|
|
| 88 |
results = model.predict(image, imgsz=640)
|
| 89 |
raw_name = results[0].names[results[0].probs.top1]
|
| 90 |
top_class_name = raw_name.lower().strip()
|
| 91 |
|
|
|
|
| 92 |
st.session_state['prediction'] = top_class_name
|
| 93 |
st.session_state['priority_codes'] = YOLO_PROMPTS.get(top_class_name, [])
|
| 94 |
st.session_state['analyzed'] = True
|
| 95 |
|
|
|
|
| 96 |
st.rerun()
|
| 97 |
|
|
|
|
| 98 |
if st.session_state['analyzed']:
|
| 99 |
st.divider()
|
| 100 |
+
st.subheader("Langkah 2: Verifikasi Gejala")
|
| 101 |
|
| 102 |
pred = st.session_state['prediction']
|
| 103 |
codes = st.session_state['priority_codes']
|
| 104 |
|
| 105 |
+
st.info(f"🔍 **Prediksi AI:** `{pred}`")
|
| 106 |
|
| 107 |
if not codes:
|
| 108 |
+
st.warning("Tidak ada pertanyaan verifikasi khusus untuk penyakit ini. Silakan periksa **Basis Pengetahuan Lengkap** di bawah ini.")
|
| 109 |
else:
|
| 110 |
+
st.write("Silakan verifikasi diagnosis ini dengan menjawab gejala spesifik di bawah ini:")
|
| 111 |
|
|
|
|
| 112 |
user_answers = {}
|
| 113 |
for code in codes:
|
| 114 |
question = SYMPTOM_QUESTIONS.get(code, "Unknown")
|
|
|
|
| 115 |
ans = st.radio(
|
| 116 |
f"**({code})** {question}",
|
| 117 |
+
options=["Tidak", "Ya"],
|
| 118 |
+
index=0,
|
| 119 |
key=f"q_{code}"
|
| 120 |
)
|
| 121 |
+
if ans == "Ya":
|
| 122 |
user_answers[code] = True
|
| 123 |
|
| 124 |
st.divider()
|
| 125 |
+
st.markdown("**Apakah ada gejala lain?**")
|
| 126 |
|
|
|
|
| 127 |
all_symptoms_list = [f"{k}: {v}" for k,v in SYMPTOM_QUESTIONS.items()]
|
| 128 |
+
other_checks = st.multiselect("Basis Pengetahuan Lengkap", all_symptoms_list)
|
| 129 |
|
|
|
|
| 130 |
for item in other_checks:
|
| 131 |
code = item.split(":")[0].strip()
|
| 132 |
user_answers[code] = True
|
| 133 |
|
| 134 |
+
if st.button("Langkah 3: Jalankan Forward Chaining", type="primary"):
|
|
|
|
| 135 |
user_facts = set(user_answers.keys())
|
| 136 |
|
| 137 |
if not user_facts:
|
| 138 |
+
st.error("**Tidak Meyakinkan:** Anda memilih 'Tidak' untuk semuanya.")
|
| 139 |
else:
|
| 140 |
matches = []
|
| 141 |
for rule in PRODUCTION_RULES:
|
|
|
|
| 144 |
matches.append(rule)
|
| 145 |
|
| 146 |
if matches:
|
| 147 |
+
st.success("**Penyakit Terkonfirmasi!**")
|
| 148 |
for m in matches:
|
| 149 |
st.markdown(f"""
|
| 150 |
### {m['disease']}
|
|
|
|
| 152 |
- **Logic:** Found all required symptoms {m['symptoms']}
|
| 153 |
""")
|
| 154 |
else:
|
| 155 |
+
st.error("**Tidak Ditemukan Kecocokan Pasti**")
|
| 156 |
+
st.write("Gejala yang Anda pilih tidak sepenuhnya sesuai dengan aturan ketat mana pun dalam basis pengetahuan.")
|