joytheslothh commited on
Commit
61da00d
·
1 Parent(s): 7c45e19

fix: add root app.py and HF Space config (Streamlit SDK)

Browse files
Files changed (3) hide show
  1. README.md +11 -0
  2. app.py +204 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,3 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
1
  # 🦠 Bacsense 2.0
2
 
3
  ![Bacsense Banner](https://img.shields.io/badge/Bacsense-2.0-13a4ec?style=for-the-badge)
 
1
+ ---
2
+ title: BacSense API
3
+ emoji: 🦠
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: streamlit
7
+ sdk_version: "1.32.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
  # 🦠 Bacsense 2.0
13
 
14
  ![Bacsense Banner](https://img.shields.io/badge/Bacsense-2.0-13a4ec?style=for-the-badge)
app.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+ import os
4
+ import sys
5
+ import tempfile
6
+ import pandas as pd
7
+
8
+ # Ensure bacsense_v2_package is importable from the repo root
9
+ sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
10
+ from bacsense_v2_package.inference import BacSense
11
+
12
+ # Precaution dictionary
13
+ PRECAUTIONS = {
14
+ "Escherichia coli": "Indicator of fecal contamination. \n\n**Precautions/Actions:** Boil water immediately before consumption. Source trace to find sewage leaks. Do not use for washing open wounds.",
15
+ "Pseudomonas aeruginosa": "Opportunistic pathogen resistant to many sanitizers. \n\n**Precautions/Actions:** Ensure water chlorination levels are adequate. Can cause severe infections in immunocompromised individuals. Avoid contact with eyes or ears.",
16
+ "Enterococcus faecalis": "Indicates prolonged fecal contamination. Very resilient. \n\n**Precautions/Actions:** Shock chlorinate the water system. Discontinue use for drinking until negative tests are returned.",
17
+ "Clostridium perfringens": "Spore-forming bacteria, highly resistant to standard disinfection. \n\n**Precautions/Actions:** Indicates remote or past fecal contamination. UV filtration or extreme heat treatment may be required.",
18
+ "Listeria monocytogenes": "Dangerous to pregnant women and immunocompromised individuals. \n\n**Precautions/Actions:** Do not use water for food preparation or drinking. Pasteurization/boiling is required."
19
+ }
20
+
21
+ # Set page config
22
+ st.set_page_config(
23
+ page_title="BacSense v2 Dashboard",
24
+ page_icon="🦠",
25
+ layout="wide"
26
+ )
27
+
28
+ # Initialize classifier
29
+ @st.cache_resource
30
+ def get_classifier():
31
+ model_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bacsense_v2_package'))
32
+ model = BacSense(model_dir=model_dir)
33
+ model.warmup()
34
+ return model
35
+
36
+ try:
37
+ classifier = get_classifier()
38
+ except Exception as e:
39
+ st.error(f"Error loading model: {e}")
40
+ st.stop()
41
+
42
+ # Dialog function for detailed view
43
+ def render_details(item):
44
+ st.image(item["Image"], use_column_width=True)
45
+ st.markdown(f"### Predicted: **{item['Predicted Class']}**")
46
+
47
+ colA, colB = st.columns(2)
48
+ colA.metric("Confidence", f"{item['Confidence (%)']}%")
49
+ colB.metric("Risk Level", item['Risk'])
50
+
51
+ st.markdown("""---""")
52
+ st.markdown("**Bacterial Summary:**")
53
+ st.write(f"- **Gram Stain:** {item['Gram Stain']}")
54
+ st.write(f"- **Shape:** {item['Shape']}")
55
+
56
+ if item['Routed to Specialist']:
57
+ st.info(f"Ambiguous morphology triggered the Specialist SVM. Accepted: {'✅' if item['Specialist Accepted'] else '❌'}")
58
+
59
+ st.markdown("""---""")
60
+ st.markdown("**Precautions:**")
61
+ precaution_text = PRECAUTIONS.get(item['Predicted Class'], "No specific precautions available. Standard water safety protocols suggest boiling before consumption.")
62
+ st.warning(precaution_text)
63
+
64
+ if st.button("Close Summary", key="close_summary_btn"):
65
+ st.session_state.selected_item = None
66
+ st.rerun()
67
+
68
+ # Main UI
69
+ st.title("🦠 BacSense v2 Analytics Dashboard")
70
+ st.markdown("""
71
+ Welcome to the BacSense v2 Dashboard. This cascaded hybrid classifier uses **VGG16 Transfer Learning**
72
+ combined with **Hand-Crafted Feature Engineering** and an **RBF-SVM Specialist** to disambiguate waterborne pathogens.
73
+ You can safely upload **up to 60 images** at once.
74
+ """)
75
+
76
+ # Sidebar for info
77
+ with st.sidebar:
78
+ st.header("Supported Species")
79
+ st.markdown("""
80
+ - *Clostridium perfringens*
81
+ - *Enterococcus faecalis*
82
+ - *Escherichia coli*
83
+ - *Listeria monocytogenes*
84
+ - *Pseudomonas aeruginosa*
85
+ """)
86
+ st.markdown("---")
87
+ st.caption("BacSense v2 Cascaded Model")
88
+ st.caption("Overall Accuracy: 95.65%")
89
+ st.caption("Specialist AUC: 0.9863")
90
+
91
+ st.subheader("Batch Upload (Multiple Images)")
92
+ uploaded_files = st.file_uploader("Upload microscopic bacterial images...", type=["jpg", "jpeg", "png"], accept_multiple_files=True)
93
+
94
+ if "selected_item" not in st.session_state:
95
+ st.session_state.selected_item = None
96
+
97
+ if uploaded_files:
98
+ if st.session_state.selected_item is not None:
99
+ render_details(st.session_state.selected_item)
100
+ else:
101
+ uploaded_files = list(uploaded_files)[:100]
102
+ results = []
103
+
104
+ progress_container = st.container()
105
+ with progress_container:
106
+ st.write(f"Processing {len(uploaded_files)} images...")
107
+ progress_bar = st.progress(0)
108
+ status_text = st.empty()
109
+
110
+ for i, uploaded_file in enumerate(uploaded_files):
111
+ status_text.text(f"Analyzing [{i+1}/{len(uploaded_files)}]: {uploaded_file.name}...")
112
+
113
+ image = Image.open(uploaded_file)
114
+ fd, temp_path = tempfile.mkstemp(suffix=".png")
115
+ if image.mode != 'RGB':
116
+ image = image.convert('RGB')
117
+
118
+ with os.fdopen(fd, 'wb') as f:
119
+ image.save(f, format="PNG")
120
+
121
+ try:
122
+ prediction = classifier.predict(temp_path)
123
+ confidence_pct = prediction['confidence'] * 100 if prediction['confidence'] <= 1.0 else prediction['confidence']
124
+ results.append({
125
+ "Filename": uploaded_file.name,
126
+ "Predicted Class": prediction['prediction'],
127
+ "Confidence (%)": round(confidence_pct, 2),
128
+ "Gram Stain": prediction.get('gram', 'Unknown'),
129
+ "Shape": prediction.get('shape', 'Unknown'),
130
+ "Risk": prediction.get('risk', 'Unknown'),
131
+ "Routed to Specialist": prediction.get('routed_to_specialist', False),
132
+ "Specialist Accepted": prediction.get('specialist_accepted', False),
133
+ "Image": image
134
+ })
135
+ except Exception as e:
136
+ st.error(f"Error processing {uploaded_file.name}: {e}")
137
+ finally:
138
+ if os.path.exists(temp_path):
139
+ os.remove(temp_path)
140
+
141
+ progress_bar.progress((i + 1) / len(uploaded_files))
142
+
143
+ status_text.text("Batch Processing Complete!")
144
+
145
+ if results:
146
+ df = pd.DataFrame(results)
147
+
148
+ st.markdown("---")
149
+ st.subheader("📊 Batch Analytics Summary")
150
+ col1, col2, col3, col4 = st.columns(4)
151
+
152
+ total_images = len(results)
153
+ high_risk = len(df[df["Risk"] == "High"])
154
+ routed_spec = len(df[df["Routed to Specialist"] == True])
155
+ avg_confidence = df["Confidence (%)"].mean()
156
+
157
+ col1.metric("Total Images", total_images)
158
+ col2.metric("High Target Risk", high_risk)
159
+ col3.metric("Routed to Specialist", routed_spec, help="Ambiguous cases handled by the 683-dim Specialist SVM")
160
+ col4.metric("Avg Confidence", f"{avg_confidence:.1f}%")
161
+
162
+ st.markdown("<br>", unsafe_allow_html=True)
163
+ col_chart1, col_chart2 = st.columns(2)
164
+ with col_chart1:
165
+ st.markdown("**Species Distribution**")
166
+ class_counts = df["Predicted Class"].value_counts().reset_index()
167
+ class_counts.columns = ["Species", "Count"]
168
+ st.bar_chart(class_counts.set_index("Species"))
169
+
170
+ with col_chart2:
171
+ st.markdown("**Gram Stain Breakdown**")
172
+ gram_counts = df["Gram Stain"].value_counts()
173
+ st.bar_chart(gram_counts)
174
+
175
+ st.markdown("---")
176
+ st.subheader("📋 Detailed Results Table")
177
+ st.dataframe(df.drop(columns=["Image"]), use_container_width=True)
178
+
179
+ st.markdown("---")
180
+ st.subheader("🖼️ Processed Image Gallery")
181
+ st.caption("Click on 'View Summary' underneath any image to view brief details and precautions for the detected pathogen.")
182
+
183
+ filter_class = st.selectbox("Filter gallery by predicted species:", ["All"] + sorted(df["Predicted Class"].unique().tolist()))
184
+ filtered_results = results if filter_class == "All" else [r for r in results if r["Predicted Class"] == filter_class]
185
+
186
+ cols_per_row = 4
187
+ for i in range(0, len(filtered_results), cols_per_row):
188
+ cols = st.columns(cols_per_row)
189
+ for j in range(cols_per_row):
190
+ if i + j < len(filtered_results):
191
+ item = filtered_results[i + j]
192
+ with cols[j]:
193
+ st.image(item["Image"], use_column_width=True)
194
+ st.markdown(f"**{item['Predicted Class']}**")
195
+ det_col1, det_col2 = st.columns(2)
196
+ det_col1.markdown(f"<small>{item['Confidence (%)']}% Conf</small>", unsafe_allow_html=True)
197
+ if item["Routed to Specialist"]:
198
+ det_col2.markdown(f"<small>Specialist: {'✅' if item['Specialist Accepted'] else '❌'}</small>", unsafe_allow_html=True)
199
+ if st.button("View Summary", key=f"details_btn_{i}_{j}"):
200
+ st.session_state.selected_item = item
201
+ st.rerun()
202
+
203
+ else:
204
+ st.info("Please upload one or more images (up to 60) to start the batch analysis.")
requirements.txt CHANGED
@@ -5,6 +5,8 @@ opencv-python-headless>=4.8.0
5
  numpy>=1.24.0
6
  Pillow>=9.5.0
7
  scipy>=1.11.0
 
 
8
  fastapi
9
  uvicorn
10
  python-multipart
 
5
  numpy>=1.24.0
6
  Pillow>=9.5.0
7
  scipy>=1.11.0
8
+ streamlit>=1.32.0
9
+ pandas>=2.0.0
10
  fastapi
11
  uvicorn
12
  python-multipart