Bio-signal-chatbot / src /streamlit_app.py
niol08's picture
Update src/streamlit_app.py
b0e7881 verified
import os
import streamlit as st
from dotenv import load_dotenv
from model_loader import load_mitbih_model, load_pcg_model, load_emg_model, load_vag_model
from chatbot import analyze_signal
from util import analyze_pcg_signal, analyze_emg_signal, predict_vag_from_features,vag_to_features
load_dotenv()
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
st.set_page_config(page_title="Biosignal Chatbot", page_icon="🩺", layout="centered")
st.title("🩺 Biosignal Diagnostic Chatbot")
st.components.v1.html("""
<script>
// Force file uploader refresh and improve functionality
function refreshUploaders() {
// Find all file upload areas
const uploaders = document.querySelectorAll('[data-testid="stFileUploader"]');
uploaders.forEach((uploader, index) => {
// Add visual feedback
const dropzone = uploader.querySelector('[data-testid="stFileUploadDropzone"]');
if (dropzone) {
dropzone.style.border = '2px dashed #ff6b6b';
dropzone.style.backgroundColor = '#f8f9fa';
dropzone.style.transition = 'all 0.3s ease';
// Add hover effects
dropzone.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.backgroundColor = '#e3f2fd';
this.style.border = '2px dashed #2196f3';
});
dropzone.addEventListener('dragleave', function(e) {
this.style.backgroundColor = '#f8f9fa';
this.style.border = '2px dashed #ff6b6b';
});
// Force refresh on file change
const input = uploader.querySelector('input[type="file"]');
if (input) {
input.addEventListener('change', function(e) {
console.log('File selected:', e.target.files[0]);
// Force a small delay then trigger Streamlit rerun
setTimeout(() => {
if (window.streamlit) {
window.streamlit.setComponentValue(Date.now());
}
// Alternative: trigger a click on a hidden button
const refreshBtn = document.querySelector('[data-testid="baseButton-secondary"]');
if (refreshBtn && refreshBtn.textContent.includes('Refresh')) {
refreshBtn.click();
}
}, 100);
});
}
}
});
}
// Run on page load and periodically
document.addEventListener('DOMContentLoaded', refreshUploaders);
setTimeout(refreshUploaders, 1000);
setTimeout(refreshUploaders, 3000);
// Monitor for new content (when tabs are switched)
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
setTimeout(refreshUploaders, 500);
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
</script>
""", height=0)
MODELS = {
"ECG": load_mitbih_model(),
"PCG": load_pcg_model(),
"EMG": load_emg_model(),
"VAG": load_vag_model(),
}
FILE_TYPES = {
"ECG": ["csv", "txt"],
"EMG": ["csv", "txt"],
"VAG": ["csv", "npy", "wav"],
"PCG": ["wav"],
}
tabs = st.tabs(["ECG", "EMG", "VAG", "PCG"])
for tab, sig in zip(tabs, ["ECG", "EMG", "VAG", "PCG"]):
with tab:
st.header(f"{sig} Analysis")
if sig == "ECG":
with st.expander("📄 ECG Data Requirements"):
st.markdown(
"- Upload a `.csv` or `.txt` file containing **256 numeric values** (single row or single column).\n"
"- Example:\n"
"```csv\n0.12\n0.15\n-0.05\n...\n```"
)
elif sig == "VAG":
with st.expander("📄 VAG Data Requirements"):
st.markdown(
"- Upload a `.csv` file **with headers** containing the following 5 features:\n"
" - `rms_amplitude`\n"
" - `peak_frequency`\n"
" - `spectral_entropy`\n"
" - `zero_crossing_rate`\n"
" - `mean_frequency`\n"
"- Example file content:\n"
"```csv\n"
"rms_amplitude,peak_frequency,spectral_entropy,zero_crossing_rate,mean_frequency\n"
"1.02,20,-1890.34,0.001,39.7\n"
"```"
)
elif sig == "EMG":
with st.expander("📄 EMG Data Requirements"):
st.markdown(
"- Upload a `.txt` or `.csv` file containing **raw EMG signal samples**.\n"
"- The model expects **at least 1,000 values** (1-second window at 1 kHz sampling).\n"
"- You can provide:\n"
" - A `.txt` file with one value per line.\n"
" - A `.csv` file with a single column of numbers.\n\n"
"- Example `.txt` file:\n"
"```txt\n"
"0.034\n"
"0.056\n"
"-0.012\n"
"...\n"
"```"
)
elif sig == "PCG":
with st.expander("📄 PCG Data Requirements"):
st.markdown(
"- Upload a `.wav` file containing a **single-channel (mono) PCG signal**.\n"
"- The model expects **at least 995 audio samples** (≈0.025s of heart sound at 44.1 kHz).\n"
"- Files longer than 995 samples will be **trimmed**; shorter ones will be **zero-padded**.\n"
"- Ensure the signal is **clean and preprocessed** (no ambient noise).\n\n"
"- Example `.wav` properties:\n"
" - Mono (1 channel)\n"
" - 44.1 kHz sampling rate\n"
" - 16-bit PCM or float32\n"
"\n"
"_Note: Do not upload `.mp3`, `.flac`, or stereo files—they may fail to process properly._"
)
uploaded = st.file_uploader(
f"Upload {sig} file",
type=FILE_TYPES[sig],
key=f"upload_{sig}"
)
if sig == "ECG" and uploaded and st.button("Run Diagnostic", key=f"run_{sig}"):
label, human, conf, gnote = analyze_signal(
uploaded, MODELS["ECG"], GEMINI_API_KEY, signal_type="ECG"
)
st.success(f"**{label}{human}**\n\nConfidence: {conf:.2%}")
if gnote:
st.markdown("### 🧠 Gemini Insight")
st.write(gnote)
elif not GEMINI_API_KEY:
st.info("Gemini key missing – no explanation.")
elif sig == "PCG" and uploaded and st.button("Run Diagnostic", key=f"run_{sig}"):
label, human, conf, gnote = analyze_pcg_signal(
uploaded, MODELS["PCG"], GEMINI_API_KEY
)
st.success(f"**{label}**\n\nConfidence: {conf:.2%}")
if gnote:
st.markdown("### 🧠 Gemini Insight")
st.write(gnote)
elif not GEMINI_API_KEY:
st.info("Gemini key missing – no explanation.")
elif sig == "EMG" and uploaded and st.button("Run Diagnostic", key=f"run_{sig}"):
human, conf, gnote = analyze_emg_signal(
uploaded, MODELS["EMG"], GEMINI_API_KEY
)
st.success(f"**{human.upper()}**\n\nConfidence: {conf:.2%}")
if gnote:
st.markdown("### 🧠 Gemini Insight")
st.write(gnote)
elif not GEMINI_API_KEY:
st.info("Gemini key missing – no explanation.")
elif sig == "VAG" and uploaded and st.button("Run Diagnostic", key=f"run_{sig}"):
label, human, conf, gnote = predict_vag_from_features(
uploaded, MODELS["VAG"], GEMINI_API_KEY
)
st.success(f"**{label}**\n\nConfidence: {conf:.2%}")
if gnote:
st.markdown("### 🧠 Gemini Insight")
st.write(gnote)
elif not GEMINI_API_KEY:
st.info("Gemini key missing – no explanation.")
else:
st.info("📤 Upload a file to begin analysis")
st.caption("© 2025 Biosignal Chatbot")