Mohammedelhakim/KidGuard_Crydetection_cryClassification
0
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)