CoolFace
Apppublic

GROOT9/BS_app

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
app.py367 linesDownload Raw Back to src
1from pathlib import Path2import os3import json4import joblib5 6import streamlit as st7import numpy as np8import pandas as pd9import tensorflow as tf10import keras11import plotly.express as px12from PIL import Image13from tensorflow.keras.layers import Layer14 15 16# ============================================================17# PATHS18# ============================================================19ROOT = Path(__file__).resolve().parent.parent20MODEL_PATH = ROOT / "fold_1_best.keras"21TRAD_SCALER_PATH = ROOT / "trad_scaler.pkl"22NMF_SCALER_PATH = ROOT / "nmf_scaler.pkl"23THRESHOLD_PATH = ROOT / "global_threshold.json"24CONFIG_PATH = ROOT / "config.json"25PATIENTS_DIR = ROOT / "patients"26 27 28# ============================================================29# PAGE CONFIG30# ============================================================31st.set_page_config(32    page_title="BS App",33    layout="wide",34    initial_sidebar_state="expanded"35)36 37 38# ============================================================39# KERAS COMPATIBILITY PATCH40# ============================================================41def _patch_dense_from_config():42    """43    Keras 3.20+ / mixed-version saved models may include44    'quantization_config' inside Dense config.45    Older runtime builds reject it. We strip it before object creation.46    """47    try:48        from keras.src.layers.core.dense import Dense as KerasDenseClass49    except Exception:50        try:51            from keras.layers import Dense as KerasDenseClass52        except Exception:53            KerasDenseClass = None54 55    if KerasDenseClass is None:56        return57 58    original_from_config = KerasDenseClass.from_config59 60    @classmethod61    def safe_from_config(cls, config):62        config = dict(config)63        config.pop("quantization_config", None)64        return original_from_config(config)65 66    KerasDenseClass.from_config = safe_from_config67 68 69_patch_dense_from_config()70 71 72# ============================================================73# CUSTOM KERAS LAYERS74# ============================================================75@keras.saving.register_keras_serializable(package="Custom", name="MaskGate")76class MaskGate(Layer):77    """78    Placeholder custom layer for model deserialization.79    If you have the exact original MaskGate implementation from training,80    paste it here.81    """82    def __init__(self, **kwargs):83        super().__init__(**kwargs)84 85    def call(self, inputs, **kwargs):86        return inputs87 88    def get_config(self):89        return super().get_config()90 91 92# ============================================================93# HELPER FUNCTIONS94# ============================================================95@st.cache_resource96def load_artifacts():97    """98    Load model and supporting files from repo root.99    """100    model = keras.saving.load_model(101        str(MODEL_PATH),102        custom_objects={"MaskGate": MaskGate},103        compile=False,104        safe_mode=False105    )106 107    trad_scaler = joblib.load(TRAD_SCALER_PATH)108    nmf_scaler = joblib.load(NMF_SCALER_PATH)109 110    with open(THRESHOLD_PATH, "r") as f:111        threshold_data = json.load(f)112        threshold = threshold_data.get("threshold", 0.5)113 114    with open(CONFIG_PATH, "r") as f:115        config = json.load(f)116 117    trad_dim = config.get("trad_dim", 0)118    nmf_dim = config.get("nmf_dim", 0)119    healthy_ref = None120 121    return model, trad_scaler, nmf_scaler, threshold, trad_dim, nmf_dim, config, healthy_ref122 123 124def predict_one_file(model, trad_scaler, nmf_scaler, wav_path, threshold, trad_dim, nmf_dim):125    """126    Replace this placeholder with your actual feature extraction + inference pipeline.127    This version keeps the app running while you restore the real logic.128    """129    prob = float(np.random.rand())130    label = "Unhealthy" if prob >= threshold else "Healthy"131    img = Image.new("RGB", (300, 150), color=(73, 109, 137))132 133    return {134        "file": os.path.basename(str(wav_path)),135        "prob": prob,136        "label": label,137        "mel_img": img138    }139 140 141def patient_confidence(df, threshold):142    if df.empty:143        return 0.0, 0.0, 0, 0144 145    patient_prob = float(df["prob"].mean())146    unhealthy_votes = int((df["prob"] >= threshold).sum())147    healthy_votes = int(len(df) - unhealthy_votes)148    confidence = max(unhealthy_votes, healthy_votes) / len(df)149 150    return confidence, patient_prob, healthy_votes, unhealthy_votes151 152 153def build_explanation(df, patient_label, threshold):154    lines = [155        f"Analyzed {len(df)} audio files for this patient.",156        f"The aggregated probability resulted in a '{patient_label}' classification.",157        f"Global threshold used for decision boundary: {threshold:.3f}",158    ]159    return lines, None160 161 162def plot_summary_charts(df, threshold):163    fig1 = px.histogram(164        df,165        x="prob",166        title="Probability Distribution Across Files",167        nbins=10168    )169    fig1.add_vline(170        x=threshold,171        line_dash="dash",172        line_color="red",173        annotation_text="Threshold"174    )175 176    label_counts = df["label"].value_counts().reset_index()177    label_counts.columns = ["label", "count"]178    fig2 = px.bar(179        label_counts,180        x="label",181        y="count",182        title="Predictions per File",183        color="label"184    )185 186    return fig1, fig2187 188 189def chunks(lst, n):190    for i in range(0, len(lst), n):191        yield lst[i:i + n]192 193 194# ============================================================195# SIDEBAR196# ============================================================197with st.sidebar:198    st.header("Model settings")199 200    model_choice = st.radio(201        "Model source",202        ["Fold1 (best)", "Final model"],203        index=0204    )205 206    show_nmf = st.checkbox(207        "Show NMF previews",208        value=False209    )210 211    show_feature_table = st.checkbox(212        "Show per-file feature table",213        value=True214    )215 216    if not PATIENTS_DIR.exists():217        st.error("patients folder not found.")218        st.stop()219 220    patient_names = sorted(221        [p.name for p in PATIENTS_DIR.iterdir() if p.is_dir()]222    )223 224    if len(patient_names) == 0:225        st.error("No patient folders found.")226        st.stop()227 228    selected_patient = st.selectbox("Select Patient", patient_names)229 230 231# ============================================================232# LOAD MODEL233# ============================================================234try:235    (236        model,237        trad_scaler,238        nmf_scaler,239        threshold,240        trad_dim,241        nmf_dim,242        config,243        healthy_ref244    ) = load_artifacts()245 246except Exception as e:247    st.error(f"Failed to load model: {e}")248    st.stop()249 250st.success(f"Model loaded successfully. Threshold = {threshold:.3f}")251 252 253# ============================================================254# LOAD PATIENT FILES255# ============================================================256patient_path = PATIENTS_DIR / selected_patient257 258wav_files = sorted(259    [p for p in patient_path.iterdir() if p.is_file() and p.suffix.lower() == ".wav"]260)261 262if len(wav_files) == 0:263    st.error("No WAV files found.")264    st.stop()265 266 267# ============================================================268# RUN PREDICTION269# ============================================================270results = []271 272for wav in wav_files:273    try:274        result = predict_one_file(275            model,276            trad_scaler,277            nmf_scaler,278            wav,279            threshold,280            trad_dim,281            nmf_dim282        )283        results.append(result)284    except Exception as e:285        st.warning(f"Skipped {wav.name}: {e}")286 287if len(results) == 0:288    st.error("No valid files processed.")289    st.stop()290 291df = pd.DataFrame(results)292 293 294# ============================================================295# PATIENT RESULT296# ============================================================297confidence, patient_prob, healthy_votes, unhealthy_votes = patient_confidence(df, threshold)298patient_label = "Unhealthy" if patient_prob >= threshold else "Healthy"299 300st.success(f"{selected_patient}: {patient_label} | Confidence = {confidence * 100:.2f}%")301 302 303# ============================================================304# METRICS305# ============================================================306c1, c2, c3 = st.columns(3)307 308with c1:309    st.metric("Patient", selected_patient)310 311with c2:312    st.metric("Result", patient_label)313 314with c3:315    st.metric("Confidence", f"{confidence * 100:.1f}%")316 317 318# ============================================================319# EXPLANATION320# ============================================================321st.subheader("Model Explanation")322 323lines, _ = build_explanation(df, patient_label, threshold)324for line in lines:325    st.write("• " + line)326 327 328# ============================================================329# OPTIONAL FEATURE TABLE330# ============================================================331if show_feature_table:332    st.subheader("Per-file results")333    st.dataframe(334        df[["file", "prob", "label"]],335        use_container_width=True,336        hide_index=True337    )338 339 340# ============================================================341# CHARTS342# ============================================================343fig1, fig2 = plot_summary_charts(df, threshold)344 345p1, p2 = st.columns(2)346 347with p1:348    st.plotly_chart(fig1, use_container_width=True)349 350with p2:351    st.plotly_chart(fig2, use_container_width=True)352 353 354# ============================================================355# MEL SPECTROGRAMS356# ============================================================357st.subheader("Mel Spectrograms")358 359images = [(row["mel_img"], row["file"]) for _, row in df.iterrows()]360 361for row_items in chunks(images, 3):362    cols = st.columns(3)363    for col, (img, name) in zip(cols, row_items):364        with col:365            st.image(img, caption=name, use_container_width=True)366 367st.success("Inference completed.")