Randi-Palguna commited on
Commit
6b49084
·
1 Parent(s): 7f4886d

feat: add app

Browse files
Files changed (2) hide show
  1. requirements.txt +6 -1
  2. src/streamlit_app.py +159 -38
requirements.txt CHANGED
@@ -1,3 +1,8 @@
1
  altair
2
  pandas
3
- streamlit
 
 
 
 
 
 
1
  altair
2
  pandas
3
+ streamlit
4
+ ultralytics
5
+ pillow
6
+ numpy
7
+ torch
8
+ numpy<2
src/streamlit_app.py CHANGED
@@ -1,40 +1,161 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ from ultralytics import YOLO
3
+ from PIL import Image
4
+ import numpy as np
5
+
6
+ # --- CONFIGURATION ---
7
+ MODEL_PATH = "best.pt"
8
+
9
+ # --- KNOWLEDGE BASE ---
10
+ SYMPTOM_QUESTIONS = {
11
+ "G1": "Daun berwana putih kecoklatan memanjang seperti mengering pada bagian tepi daun?",
12
+ "G2": "Pada pagi hari, ditemukan cairan bakteri (seperti butiran air) pada bagian terinfeksi?",
13
+ "G3": "Luka terlihat seperti anak panah di antara urat daun?",
14
+ "G4": "Luka berwarna kuning kecoklatan?",
15
+ "G5": "Pada daun terlihat transparan bila dihadapkan cahaya matahari?",
16
+ "G6": "Bercak berbentuk belah ketupat (Diamond shape)?",
17
+ "G7": "Bagian tengah bercak abu-abu/putih, tepi berwarna kecoklatan?",
18
+ "G8": "Infeksi pada malai/leher berwarna abu-abu?",
19
+ "G13": "Tanaman kerdil dan daun berubah warna (hijau -> jingga/kemerahan)?",
20
+ "G025": "Daun menguning, menggulung, mengering dan menjadi layu?",
21
+ "G026": "Bibit menjadi layu (kresek) tapi sulit dicabut?",
22
+ "G027": "Warna luka bercak jingga kekuningan dari ujung ke pangkal?",
23
+ "G028": "Ada bulatan kecil berwarna kuning pada pelepah daun?"
24
+ }
25
+
26
+ YOLO_PROMPTS = {
27
+ "bacterial_leaf_blight": ["G1", "G2"],
28
+ "bacterial_leaf_streak": ["G3", "G4", "G5"],
29
+ "blast": ["G6", "G7", "G8"],
30
+ "tungro": ["G13"],
31
+ "brown_spot": [],
32
+ "downy_mildew": [],
33
+ "dead_heart": [],
34
+ "hispa": [],
35
+ "normal": []
36
+ }
37
+
38
+ PRODUCTION_RULES = [
39
+ {"disease": "Bacterial Leaf Blight (Hawar Daun Bakteri)", "symptoms": ["G1", "G2"], "source": "Literatur 1"},
40
+ {"disease": "Bacterial Leaf Streak (Bakteri Daun Bergaris)", "symptoms": ["G3", "G4", "G5"], "source": "Literatur 1"},
41
+ {"disease": "Blast (Blas)", "symptoms": ["G6", "G7"], "source": "Literatur 1"},
42
+ {"disease": "Neck Blast (Blas Leher)", "symptoms": ["G8"], "source": "Literatur 1"},
43
+ {"disease": "Tungro", "symptoms": ["G13"], "source": "Literatur 1"},
44
+ {"disease": "Bacterial Leaf Blight (Alt. Definition)", "symptoms": ["G025", "G026", "G027", "G028"], "source": "Literatur 2"}
45
+ ]
46
+
47
+ # --- MODEL LOADING (Cached so it doesn't reload every click) ---
48
+ @st.cache_resource
49
+ def load_model():
50
+ try:
51
+ return YOLO(MODEL_PATH)
52
+ except Exception as e:
53
+ st.error(f"Error loading model: {e}")
54
+ return None
55
+
56
+ model = load_model()
57
+
58
+ # --- APP LAYOUT ---
59
+ st.title("🌾 Rice Doctor: Hybrid AI System")
60
+ st.markdown("""
61
+ 1. **AI Inference**: Detects disease from image.
62
+ 2. **Expert System**: Verifies diagnosis using Forward Chaining logic.
63
+ """)
64
+
65
+ # Initialize Session State to keep data across re-runs
66
+ if 'prediction' not in st.session_state:
67
+ st.session_state['prediction'] = None
68
+ if 'priority_codes' not in st.session_state:
69
+ st.session_state['priority_codes'] = []
70
+ if 'analyzed' not in st.session_state:
71
+ st.session_state['analyzed'] = False
72
+
73
+ # --- STEP 1: UPLOAD AND ANALYZE ---
74
+ uploaded_file = st.file_uploader("Step 1: Upload Leaf Image", type=["jpg", "png", "jpeg"])
75
+
76
+ if uploaded_file is not None:
77
+ # Display image
78
+ image = Image.open(uploaded_file)
79
+ st.image(image, caption="Uploaded Image", use_container_width=True)
80
+
81
+ if st.button("Analyze & Generate Questions", type="primary"):
82
+ if model:
83
+ with st.spinner("Analyzing..."):
84
+ # Run Inference
85
+ results = model.predict(image, imgsz=640)
86
+ raw_name = results[0].names[results[0].probs.top1]
87
+ top_class_name = raw_name.lower().strip()
88
+
89
+ # Save to session state
90
+ st.session_state['prediction'] = top_class_name
91
+ st.session_state['priority_codes'] = YOLO_PROMPTS.get(top_class_name, [])
92
+ st.session_state['analyzed'] = True
93
+
94
+ # Force rerun to show step 2
95
+ st.rerun()
96
+
97
+ # --- STEP 2: DYNAMIC QUESTIONS ---
98
+ if st.session_state['analyzed']:
99
+ st.divider()
100
+ st.subheader("Step 2: Symptom Verification")
101
+
102
+ pred = st.session_state['prediction']
103
+ codes = st.session_state['priority_codes']
104
+
105
+ st.info(f"🔍 **AI Prediction:** `{pred}`")
106
+
107
+ if not codes:
108
+ st.warning("⚠️ No specific verification questions defined for this disease. Please check the **Full Knowledge Base** below.")
109
+ else:
110
+ st.write("Please verify this diagnosis by answering the specific symptoms below:")
111
+
112
+ # Dynamic Yes/No Questions
113
+ user_answers = {}
114
+ for code in codes:
115
+ question = SYMPTOM_QUESTIONS.get(code, "Unknown")
116
+ # Unique key is important in Streamlit
117
+ ans = st.radio(
118
+ f"**({code})** {question}",
119
+ options=["No", "Yes"],
120
+ index=0, # Default to No
121
+ key=f"q_{code}"
122
+ )
123
+ if ans == "Yes":
124
+ user_answers[code] = True
125
+
126
+ st.divider()
127
+ st.markdown("**Are there other symptoms?** (If the AI missed something, check it here)")
128
+
129
+ # Full Checklist
130
+ all_symptoms_list = [f"{k}: {v}" for k,v in SYMPTOM_QUESTIONS.items()]
131
+ other_checks = st.multiselect("Full Knowledge Base", all_symptoms_list)
132
+
133
+ # Process "Other" inputs
134
+ for item in other_checks:
135
+ code = item.split(":")[0].strip()
136
+ user_answers[code] = True
137
 
138
+ # --- STEP 3: FORWARD CHAINING ---
139
+ if st.button("Step 3: Run Forward Chaining", type="primary"):
140
+ user_facts = set(user_answers.keys())
141
+
142
+ if not user_facts:
143
+ st.error("⚠️ **Inconclusive:** You selected 'No' for everything.")
144
+ else:
145
+ matches = []
146
+ for rule in PRODUCTION_RULES:
147
+ required = set(rule['symptoms'])
148
+ if required.issubset(user_facts):
149
+ matches.append(rule)
150
+
151
+ if matches:
152
+ st.success("✅ **Disease Confirmed!**")
153
+ for m in matches:
154
+ st.markdown(f"""
155
+ ### {m['disease']}
156
+ - **Source:** {m['source']}
157
+ - **Logic:** Found all required symptoms {m['symptoms']}
158
+ """)
159
+ else:
160
+ st.error(" **No Exact Match Found**")
161
+ st.write("The symptoms you selected do not perfectly fit any strict rule in the knowledge base.")