CoolFace
Apppublic

Haseeb949/fluenta-backend

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
predict_local.py160 linesDownload Raw Back to root
1"""2predict_local.py3 4Standalone script to predict a single WAV file using retrained artifacts.5 6Usage:7  python predict_local.py               # uses default test file (/mnt/data/fluent_1.wav)8  python predict_local.py path/to/file.wav9"""10import sys11from pathlib import Path12import joblib13import numpy as np14import soundfile as sf15import librosa16 17# Configuration: path to retrained artifacts (matches retrain output)18MODEL_DIR = Path(r"D:/fluenta_fresh/backend/backend/models_retrained")19MODEL_FILE = "stutter_model_retrained.pkl"20SCALER_FILE = "scaler_retrained.pkl"21FCOUNT_FILE = "feature_count.pkl"22 23THRESH = 0.8  # threshold for labeling stutter24 25# ---------- Feature extraction (same as app.py) ----------26def preprocess_audio(y: np.ndarray, sr: int, target_sr: int = 16000):27    # 1. Energy check28    if len(y) == 0: return y, sr29    rms = np.sqrt(np.mean(y**2))30    duration = len(y) / sr31    if duration < 1.0 or rms < 0.01: return np.array([]), sr32 33 34 35 36    # 2. Trim37    try:38        y_trimmed, _ = librosa.effects.trim(y, top_db=30)39        if len(y_trimmed) >= int(0.1 * sr): y = y_trimmed40    except Exception: pass41 42    # 3. Normalize43    try: y = librosa.util.normalize(y)44    except Exception: pass45 46    # 4. Resample47    if sr != target_sr:48        try:49            y = librosa.resample(y, orig_sr=sr, target_sr=target_sr)50            sr = target_sr51        except Exception: pass52 53    # 5. Padding54    min_length = int(0.5 * sr)55    if 0 < len(y) < min_length:56        y = np.pad(y, (0, max(0, min_length - len(y))), mode="constant")57    return y, sr58 59 60def extract_60_features(path: str):61    # Use librosa.load for better robustness (supports more formats than soundfile)62    y, sr = librosa.load(path, sr=16000)63    y, sr = preprocess_audio(y, sr, target_sr=16000)64 65    if len(y) == 0:66        raise ValueError("Audio is too quiet or silent. Please provide a clearer recording.")67 68    feats = []69    mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)70    feats.extend(np.mean(mfccs.T, axis=0).tolist())71    feats.extend(np.std(mfccs.T, axis=0).tolist())72    spectral_centroids = librosa.feature.spectral_centroid(y=y, sr=sr)[0]73    feats.append(float(np.mean(spectral_centroids)))74    feats.append(float(np.std(spectral_centroids)))75    spectral_rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)[0]76    feats.append(float(np.mean(spectral_rolloff)))77    feats.append(float(np.std(spectral_rolloff)))78    zcr = librosa.feature.zero_crossing_rate(y)[0]79    feats.append(float(np.mean(zcr)))80    feats.append(float(np.std(zcr)))81    chroma = librosa.feature.chroma_stft(y=y, sr=sr)82    feats.extend(np.mean(chroma.T, axis=0).tolist())83    rms = librosa.feature.rms(y=y)[0]84    feats.append(float(np.mean(rms)))85    feats.append(float(np.std(rms)))86    arr = np.asarray(feats, dtype=np.float32)87    if arr.size != 60:88        raise ValueError(f"Extracted feature length {arr.size} != expected 60")89    return arr90 91# ---------- Load artifacts ----------92def load_artifacts(model_dir: Path = MODEL_DIR):93    model_path = model_dir / MODEL_FILE94    scaler_path = model_dir / SCALER_FILE95    fcount_path = model_dir / FCOUNT_FILE96    if not model_path.exists() or not scaler_path.exists() or not fcount_path.exists():97        raise FileNotFoundError(f"Missing model/scaler/feature_count in {model_dir}")98    model = joblib.load(model_path)99    scaler = joblib.load(scaler_path)100    fcount = joblib.load(fcount_path)101    try:102        fcount = int(fcount)103    except Exception:104        fcount = int(np.asarray(fcount).item())105    return model, scaler, fcount106 107def predict_file(model, scaler, features: np.ndarray, thresh: float = THRESH):108    X_scaled = scaler.transform([features])109    raw_pred = int(model.predict(X_scaled)[0])110    p_stutter = None111    if hasattr(model, "predict_proba"):112        raw_proba = model.predict_proba(X_scaled)[0]113        idx = 1 if len(raw_proba) > 1 else 0114        if hasattr(model, "classes_"):115            classes = list(model.classes_)116            if 1 in classes:117                idx = classes.index(1)118        if 0 <= idx < len(raw_proba):119            p_stutter = float(raw_proba[idx])120    # decide label121    if p_stutter is not None:122        if p_stutter >= thresh:123            label = "Stutter"124            confidence = p_stutter125        else:126            label = "Non-Stutter"127            confidence = 1.0 - p_stutter128    else:129        label = "Stutter" if raw_pred == 1 else "Non-Stutter"130        confidence = None131    return raw_pred, confidence, label, p_stutter132 133# ---------- Main ----------134def main():135    # default test file (uploaded earlier)136    default_fp = Path("/mnt/data/fluent_1.wav")137    fp = Path(sys.argv[1]) if len(sys.argv) > 1 else default_fp138    if not fp.exists():139        print("File not found:", fp)140        return141    model, scaler, fcount = load_artifacts(MODEL_DIR)142    print("Loaded model from:", MODEL_DIR)143    feats = extract_60_features(str(fp))144    raw_pred, confidence, label, p_stutter = predict_file(model, scaler, feats, thresh=THRESH)145    print("=== Prediction ===")146    print("File:", fp)147    print("Raw predicted class:", raw_pred)148    if p_stutter is not None:149        print(f"Probability(stutter): {p_stutter:.4f}")150        print(f"Used threshold: {THRESH}")151    print("Final label:", label)152    if confidence is not None:153        print(f"Confidence (used for label): {confidence*100:.2f}%")154    else:155        print("Confidence: not available (model.predict_proba missing)")156    print("==================")157 158if __name__ == "__main__":159    main()160