Spaces:
Sleeping
Sleeping
File size: 10,173 Bytes
c851312 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | import streamlit as st
from PIL import Image
import os
import sys
import tempfile
import pandas as pd
# Add the parent directory to sys.path to import bacsense_v2_package
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from bacsense_v2_package.inference import BacSense
# Precaution dictionary
PRECAUTIONS = {
"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.",
"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.",
"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.",
"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.",
"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."
}
# Set page config
st.set_page_config(
page_title="BacSense v2 Dashboard",
page_icon="🦠",
layout="wide"
)
# Initialize classifier
@st.cache_resource
def get_classifier():
model_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'bacsense_v2_package'))
model = BacSense(model_dir=model_dir)
model.warmup()
return model
try:
classifier = get_classifier()
except Exception as e:
st.error(f"Error loading model: {e}")
st.stop()
# Dialog function for detailed view
# Fallback for older Streamlit versions that lack st.dialog
def render_details(item):
st.image(item["Image"], use_column_width=True)
st.markdown(f"### Predicted: **{item['Predicted Class']}**")
colA, colB = st.columns(2)
colA.metric("Confidence", f"{item['Confidence (%)']}%")
colB.metric("Risk Level", item['Risk'])
st.markdown("""---""")
st.markdown("**Bacterial Summary:**")
st.write(f"- **Gram Stain:** {item['Gram Stain']}")
st.write(f"- **Shape:** {item['Shape']}")
if item['Routed to Specialist']:
st.info(f"Ambiguous morphology triggered the Specialist SVM. Accepted: {'✅' if item['Specialist Accepted'] else '❌'}")
st.markdown("""---""")
st.markdown("**Precautions:**")
precaution_text = PRECAUTIONS.get(item['Predicted Class'], "No specific precautions available. Standard water safety protocols suggest boiling before consumption.")
st.warning(precaution_text)
if st.button("Close Summary", key="close_summary_btn"):
st.session_state.selected_item = None
st.rerun()
# Main UI
st.title("🦠 BacSense v2 Analytics Dashboard")
st.markdown("""
Welcome to the BacSense v2 Dashboard. This cascaded hybrid classifier uses **VGG16 Transfer Learning**
combined with **Hand-Crafted Feature Engineering** and an **RBF-SVM Specialist** to disambiguate waterborne pathogens.
You can safely upload **up to 60 images** at once.
""")
# Sidebar for info
with st.sidebar:
st.header("Supported Species")
st.markdown("""
- *Clostridium perfringens*
- *Enterococcus faecalis*
- *Escherichia coli*
- *Listeria monocytogenes*
- *Pseudomonas aeruginosa*
""")
st.markdown("---")
st.caption("BacSense v2 Cascaded Model")
st.caption("Overall Accuracy: 95.65%")
st.caption("Specialist AUC: 0.9863")
st.subheader("Batch Upload (Multiple Images)")
uploaded_files = st.file_uploader("Upload microscopic bacterial images...", type=["jpg", "jpeg", "png"], accept_multiple_files=True)
if "selected_item" not in st.session_state:
st.session_state.selected_item = None
if uploaded_files:
# Check if we are viewing details
if st.session_state.selected_item is not None:
render_details(st.session_state.selected_item)
else:
# Filter to 60 images to prevent abuse if needed, or just process however many there are
uploaded_files = list(uploaded_files)[:100] # Safe upper limit
results = []
# Progress container
progress_container = st.container()
with progress_container:
st.write(f"Processing {len(uploaded_files)} images...")
progress_bar = st.progress(0)
status_text = st.empty()
for i, uploaded_file in enumerate(uploaded_files):
status_text.text(f"Analyzing [{i+1}/{len(uploaded_files)}]: {uploaded_file.name}...")
# Load image
image = Image.open(uploaded_file)
# Save temp file
fd, temp_path = tempfile.mkstemp(suffix=".png")
if image.mode != 'RGB':
image = image.convert('RGB')
with os.fdopen(fd, 'wb') as f:
image.save(f, format="PNG")
try:
# Classify using BacSense cascaded model
prediction = classifier.predict(temp_path)
confidence_pct = prediction['confidence'] * 100 if prediction['confidence'] <= 1.0 else prediction['confidence']
results.append({
"Filename": uploaded_file.name,
"Predicted Class": prediction['prediction'],
"Confidence (%)": round(confidence_pct, 2),
"Gram Stain": prediction.get('gram', 'Unknown'),
"Shape": prediction.get('shape', 'Unknown'),
"Risk": prediction.get('risk', 'Unknown'),
"Routed to Specialist": prediction.get('routed_to_specialist', False),
"Specialist Accepted": prediction.get('specialist_accepted', False),
"Image": image
})
except Exception as e:
st.error(f"Error processing {uploaded_file.name}: {e}")
finally:
# Cleanup
if os.path.exists(temp_path):
os.remove(temp_path)
# Update progress
progress_bar.progress((i + 1) / len(uploaded_files))
status_text.text("Batch Processing Complete!")
if results:
df = pd.DataFrame(results)
# 1. Top Level Metrics
st.markdown("---")
st.subheader("📊 Batch Analytics Summary")
col1, col2, col3, col4 = st.columns(4)
total_images = len(results)
high_risk = len(df[df["Risk"] == "High"])
routed_spec = len(df[df["Routed to Specialist"] == True])
avg_confidence = df["Confidence (%)"].mean()
col1.metric("Total Images", total_images)
col2.metric("High Target Risk", high_risk)
col3.metric("Routed to Specialist", routed_spec, help="Ambiguous cases handled by the 683-dim Specialist SVM")
col4.metric("Avg Confidence", f"{avg_confidence:.1f}%")
# 2. Charts
st.markdown("<br>", unsafe_allow_html=True)
col_chart1, col_chart2 = st.columns(2)
with col_chart1:
st.markdown("**Species Distribution**")
class_counts = df["Predicted Class"].value_counts().reset_index()
class_counts.columns = ["Species", "Count"]
st.bar_chart(class_counts.set_index("Species"))
with col_chart2:
st.markdown("**Gram Stain Breakdown**")
gram_counts = df["Gram Stain"].value_counts()
st.bar_chart(gram_counts)
# 3. Data Table
st.markdown("---")
st.subheader("📋 Detailed Results Table")
st.dataframe(df.drop(columns=["Image"]), use_container_width=True)
# 4. Filterable Image Gallery
st.markdown("---")
st.subheader("🖼️ Processed Image Gallery")
st.caption("Click on 'View Summary' underneath any image to view brief details and precautions for the detected pathogen.")
filter_class = st.selectbox("Filter gallery by predicted species:", ["All"] + sorted(df["Predicted Class"].unique().tolist()))
filtered_results = results if filter_class == "All" else [r for r in results if r["Predicted Class"] == filter_class]
# Display images in a grid
cols_per_row = 4
for i in range(0, len(filtered_results), cols_per_row):
cols = st.columns(cols_per_row)
for j in range(cols_per_row):
if i + j < len(filtered_results):
item = filtered_results[i + j]
with cols[j]:
st.image(item["Image"], use_column_width=True)
st.markdown(f"**{item['Predicted Class']}**")
# Add pills for details
det_col1, det_col2 = st.columns(2)
det_col1.markdown(f"<small>{item['Confidence (%)']}% Conf</small>", unsafe_allow_html=True)
if item["Routed to Specialist"]:
det_col2.markdown(f"<small>Specialist: {'✅' if item['Specialist Accepted'] else '❌'}</small>", unsafe_allow_html=True)
if st.button("View Summary", key=f"details_btn_{i}_{j}"):
st.session_state.selected_item = item
st.rerun()
else:
st.info("Please upload one or more images (up to 60) to start the batch analysis.")
|