CoolFace
Apppublic

Mohammedelhakim/KidGuard_Crydetection_cryClassification

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
app.py209 linesDownload Raw Back to root
1# app.py2from fastapi import FastAPI, UploadFile, File, HTTPException3import traceback4import numpy as np5import librosa6import joblib7import tempfile8import os9import tensorflow as tf10import tensorflow_hub as hub11 12# =========================13# Configuration14# =========================15SR = 1600016 17DETECTOR_MODEL_PATH = "detection_models/yamnet_lr_model.joblib"18DETECTOR_SCALER_PATH = "detection_models/scaler_yamnet.pkl"19DETECTOR_PCA_PATH = "detection_models/pca_yamnet.pkl"20 21CLASS_ENSEMBLE_PATH = "classification_models/babycry_ensemble.pkl"22CLASS_SCALER_PATH = "classification_models/scaler.pkl"23CLASS_SELECTOR_PATH = "classification_models/feature_selector.pkl"24CLASS_LE_PATH = "classification_models/label_encoder.pkl"25 26# =========================27# Load models (ONCE)28# =========================29yamnet = hub.load("https://tfhub.dev/google/yamnet/1")30 31det_model = joblib.load(DETECTOR_MODEL_PATH)32det_scaler = joblib.load(DETECTOR_SCALER_PATH)33det_pca = joblib.load(DETECTOR_PCA_PATH)34 35ensemble = joblib.load(CLASS_ENSEMBLE_PATH)36cls_scaler = joblib.load(CLASS_SCALER_PATH)37feature_selector = joblib.load(CLASS_SELECTOR_PATH)38label_encoder = joblib.load(CLASS_LE_PATH)39 40# =========================41# Feature Extraction42# =========================43def extract_yamnet_embedding(path):44    wav, _ = librosa.load(path, sr=SR, mono=True)45    waveform = tf.convert_to_tensor(wav, dtype=tf.float32)46 47    _, embeddings, _ = yamnet(waveform)48    emb = embeddings.numpy()49 50    mean_emb = np.mean(emb, axis=0)51    std_emb = np.std(emb, axis=0)52 53    return np.concatenate([mean_emb, std_emb]).reshape(1, -1)54 55def extract_classification_features(path):56 57    y, sr = librosa.load(path, sr=SR)58    stft = np.abs(librosa.stft(y))59 60    mfcc = np.mean(librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40), axis=1)61    chroma = np.mean(librosa.feature.chroma_stft(S=stft, sr=sr), axis=1)62    mel = np.mean(librosa.feature.melspectrogram(y=y, sr=sr), axis=1)63    contrast = np.mean(librosa.feature.spectral_contrast(S=stft, sr=sr), axis=1)64    tonnetz = np.mean(librosa.feature.tonnetz(y=librosa.effects.harmonic(y), sr=sr), axis=1)65 66        # Time-domain features (ensure 1D)67    zero_crossing = np.mean(librosa.feature.zero_crossing_rate(y))68    energy = np.mean(librosa.feature.rms(y=y))69        70        # Spectral features (ensure 1D)71    spec_centroid = np.mean(librosa.feature.spectral_centroid(y=y, sr=sr))72    spec_bandwidth = np.mean(librosa.feature.spectral_bandwidth(y=y, sr=sr))73    spec_rolloff = np.mean(librosa.feature.spectral_rolloff(y=y, sr=sr))74    spec_flatness = np.mean(librosa.feature.spectral_flatness(y=y))75 76    combined_features = np.concatenate([77            mfcc[:40],              # First 40 MFCCs78            chroma[:12],            # 12 chroma features79            mel[:40],               # First 40 mel features80            contrast[:7],           # 7 contrast features81            tonnetz[:6],            # 6 tonnetz features82            [zero_crossing],        # 1 feature83            [energy],               # 1 feature84            [spec_centroid],        # 1 feature85            [spec_bandwidth],       # 1 feature86            [spec_rolloff],         # 1 feature87            [spec_flatness]         # 1 feature88        ])89 90    return combined_features.reshape(1,-1)91 92 93# =========================94# Detection & Classification95# =========================96def detect_is_cry(path, threshold):97    feat = extract_yamnet_embedding(path)98    feat = det_scaler.transform(feat)99    feat = det_pca.transform(feat)100 101    prob = det_model.predict_proba(feat)[0][0]102 103    is_cry = bool(prob >= threshold)   104    return is_cry, float(prob)105 106 107def classify_cry(path, conf_threshold):108    feat = extract_classification_features(path)109    current_len = feat.shape[1]110    expected_len = getattr(cls_scaler, "n_features_in_", None)111 112    if expected_len is not None and current_len != expected_len:113        raise HTTPException(114            status_code=500,115            detail=f"Feature length mismatch: got {current_len}, expected {expected_len}"116        )117    print("feat shape at classify_cry:", feat.shape)  # should be (1, 111)118    print("scaler expects:", cls_scaler.n_features_in_)  # should be 111119 120    feat_scaled = cls_scaler.transform(feat)121    feat_selector = feature_selector.transform(feat_scaled)122 123    probs = ensemble.predict_proba(feat_selector)[0]124    max_prob = float(np.max(probs))125 126    if max_prob < conf_threshold:127        return "Normal / Not a Cry", None, max_prob128 129    label = label_encoder.inverse_transform([np.argmax(probs)])[0]130    return label, probs.tolist(), max_prob131 132# =========================133# FastAPI App134# =========================135app = FastAPI(136    title="Baby Cry Detection & Classification API",137    version="1.0"138)139 140@app.post("/predict")141async def predict(142    file: UploadFile = File(...),143    detection_threshold: float = 0.5,144    classification_threshold: float = 0.6145):146    if not file.filename.lower().endswith((".wav", ".mp3", ".flac", ".ogg")):147        raise HTTPException(status_code=400, detail="Invalid audio format")148 149    with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:150        tmp.write(await file.read())151        tmp_path = tmp.name152 153    try:154        try:155            PARENT_ADVICE = {156                "belly_pain": {157                    "advice": "Your baby might have abdominal pain or wind problems. Tummy massage, bicycling legs, and appropriate burping after feeding could be tried."158                        },159                "burping": {160                "advice": "It is possible that your baby needs to burp. Place your baby in an upright position and pat or massage the back for some minutes."161                    },162                "discomfort": {163                "advice": "Check whether the baby is uncomfortable due to a wet nappy, uncomfortable clothing, room temperature, or need for calming."164            },165            "hungry": {166            "advice": "Your baby might be hungry. You could offer food if the baby is near to her usual feeding time."167        },168            "tired": {169            "advice": "Your baby might be tired. It could help if you reduce stimulation, darken the room, and assist your baby to fall asleep."170        }171                            }172            is_cry, cry_prob = detect_is_cry(tmp_path, detection_threshold)173 174            response = {175                "filename": file.filename,176                "cry_probability": cry_prob,177                "is_cry": is_cry,178            }179 180            if not is_cry:181                response["result"] = "Not a cry"182                return response183 184            label, probs, confidence = classify_cry(185                tmp_path,186                classification_threshold187            )188            advice = PARENT_ADVICE.get(189            label,190            {"advice": "Please monitor your baby and consult a pediatrician if crying persists."}191            )192            response.update({193                "result": label,194                "confidence": confidence,195                "class_probabilities": probs,196                "parent_advice": advice["advice"]197            })198 199            return response200        except Exception as e:201            # Log full traceback to the server console202            traceback.print_exc()203            # Return the error message so you see it in the client204            raise HTTPException(205                status_code=500,206                detail=f"Prediction failed: {e}"207            )208    finally:209        os.remove(tmp_path)