Spaces:
Sleeping
Sleeping
File size: 10,469 Bytes
61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d 2d03f46 61da00d | 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 | import streamlit as st
from PIL import Image
import os
import sys
import tempfile
import pandas as pd
import time
# Ensure bacsense_v2_package is importable from the repo root
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
# 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"
)
# Lazy load — BacSense (and TensorFlow) only imported when first image is uploaded
@st.cache_resource(show_spinner="⏳ Loading BacSense model — this may take ~60s on first run...")
def get_classifier():
from bacsense_v2_package.inference import BacSense
model_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bacsense_v2_package'))
return BacSense(model_dir=model_dir)
# Dialog function for detailed view
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 "is_first_run" not in st.session_state:
st.session_state.is_first_run = True
if uploaded_files:
if st.session_state.selected_item is not None:
render_details(st.session_state.selected_item)
else:
uploaded_files = list(uploaded_files)[:100]
results = []
progress_container = st.container()
with progress_container:
st.write(f"Processing {len(uploaded_files)} images...")
progress_bar = st.progress(0)
status_text = st.empty()
timer_text = st.empty()
# Calculate estimated time: 6s per image + 120s for cold start on HF
per_image_time = 6
cold_start_time = 120 if st.session_state.is_first_run else 10
total_seconds = int(len(uploaded_files) * per_image_time + cold_start_time)
start_time = time.time()
for i, uploaded_file in enumerate(uploaded_files):
current_elapsed = time.time() - start_time
remaining = max(1, total_seconds - int(current_elapsed))
if st.session_state.is_first_run and i == 0:
status_text.info(f"⏳ **Initializing VGG16 Model...** (This may take up to 2 mins on first run). \n\n Analyzing: {uploaded_file.name}")
else:
status_text.text(f"Analyzing [{i+1}/{len(uploaded_files)}]: {uploaded_file.name}...")
timer_text.markdown(f"⏱️ **Estimated time remaining: ~{remaining} seconds**")
image = Image.open(uploaded_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:
# Retrieve the classifier (lazy-load)
classifier = get_classifier()
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:
if os.path.exists(temp_path):
os.remove(temp_path)
progress_bar.progress((i + 1) / len(uploaded_files))
st.session_state.is_first_run = False
timer_text.empty()
status_text.text("Batch Processing Complete!")
if results:
df = pd.DataFrame(results)
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}%")
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)
st.markdown("---")
st.subheader("📋 Detailed Results Table")
st.dataframe(df.drop(columns=["Image"]), use_container_width=True)
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]
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']}**")
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.")
|