mohsinramzan commited on
Commit
d3e1c6c
Β·
verified Β·
1 Parent(s): 19f0302

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -0
app.py CHANGED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pickle
3
+ import tempfile
4
+ import numpy as np
5
+ import librosa
6
+ import tensorflow as tf
7
+ from fastapi import FastAPI, UploadFile, File, HTTPException
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+
10
+ # ── Constants (from model_config.json) ────────────────────────────────────────
11
+ SR = 22050
12
+ DURATION = 3
13
+ HOP_LENGTH = 512
14
+ TIME_FRAMES = 130
15
+ N_BINS = 84
16
+ N_MFCC = 40
17
+ CLASSES = ["Muwa", "east", "loyal", "poems", "rai"]
18
+
19
+ # ── App ───────────────────────────────────────────────────────────────────────
20
+ app = FastAPI(title="Audio Classifier API")
21
+
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"],
25
+ allow_methods=["*"],
26
+ allow_headers=["*"],
27
+ )
28
+
29
+ # ── Load artifacts on startup ──────────────────────────────────────────────────
30
+ @app.on_event("startup")
31
+ async def load_artifacts():
32
+ global model, scaler_cqt, scaler_mfcc, label_encoder
33
+
34
+ model = tf.keras.models.load_model("model_artifacts/best_cross_attn.keras", compile=False)
35
+ print("βœ… Model loaded")
36
+
37
+ with open("model_artifacts/scaler_cqt.pkl", "rb") as f:
38
+ scaler_cqt = pickle.load(f)
39
+
40
+ with open("model_artifacts/scaler_mfcc.pkl", "rb") as f:
41
+ scaler_mfcc = pickle.load(f)
42
+
43
+ with open("model_artifacts/label_encoder.pkl", "rb") as f:
44
+ label_encoder = pickle.load(f)
45
+
46
+ print("βœ… Scalers and label encoder loaded")
47
+
48
+ # ── Feature extraction ─────────────────────────────────────────────────────────
49
+ def extract_features(audio_path: str):
50
+ y, sr = librosa.load(audio_path, sr=SR, duration=DURATION)
51
+
52
+ # Pad if audio is shorter than expected
53
+ expected_samples = SR * DURATION
54
+ if len(y) < expected_samples:
55
+ y = np.pad(y, (0, expected_samples - len(y)))
56
+
57
+ # CQT β†’ (TIME_FRAMES, N_BINS)
58
+ cqt = np.abs(librosa.cqt(y, sr=sr, hop_length=HOP_LENGTH, n_bins=N_BINS))
59
+ cqt = librosa.util.fix_length(cqt, size=TIME_FRAMES, axis=1).T # (130, 84)
60
+
61
+ # Apply scaler (fit was on 2D, so flatten β†’ scale β†’ reshape)
62
+ cqt_shape = cqt.shape
63
+ cqt = scaler_cqt.transform(cqt.reshape(-1, cqt_shape[-1])).reshape(cqt_shape)
64
+
65
+ # MFCC β†’ (TIME_FRAMES, N_MFCC)
66
+ mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=N_MFCC, hop_length=HOP_LENGTH)
67
+ mfcc = librosa.util.fix_length(mfcc, size=TIME_FRAMES, axis=1).T # (130, 40)
68
+
69
+ # Apply scaler
70
+ mfcc_shape = mfcc.shape
71
+ mfcc = scaler_mfcc.transform(mfcc.reshape(-1, mfcc_shape[-1])).reshape(mfcc_shape)
72
+
73
+ # Add batch dimension β†’ (1, 130, 84) and (1, 130, 40)
74
+ return cqt[np.newaxis], mfcc[np.newaxis]
75
+
76
+ # ── Predict endpoint ───────────────────────────────────────────────────────────
77
+ @app.post("/predict")
78
+ async def predict(file: UploadFile = File(...)):
79
+ if not file.filename.endswith((".wav", ".mp3", ".flac", ".ogg")):
80
+ raise HTTPException(status_code=400, detail="Unsupported format. Use wav, mp3, flac, or ogg.")
81
+
82
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp:
83
+ tmp.write(await file.read())
84
+ tmp_path = tmp.name
85
+
86
+ try:
87
+ cqt, mfcc = extract_features(tmp_path)
88
+ preds = model.predict([cqt, mfcc])[0] # (5,)
89
+ top_idx = int(np.argmax(preds))
90
+
91
+ # Use label_encoder if sklearn LabelEncoder, else fall back to CLASSES list
92
+ try:
93
+ label = label_encoder.inverse_transform([top_idx])[0]
94
+ except Exception:
95
+ label = CLASSES[top_idx]
96
+
97
+ return {
98
+ "label": label,
99
+ "confidence": round(float(preds[top_idx]), 4),
100
+ "all_scores": {
101
+ CLASSES[i]: round(float(p), 4) for i, p in enumerate(preds)
102
+ }
103
+ }
104
+
105
+ except Exception as e:
106
+ raise HTTPException(status_code=500, detail=f"Inference error: {str(e)}")
107
+
108
+ finally:
109
+ os.remove(tmp_path)
110
+
111
+ # ── Health endpoints ───────────────────────────────────────────────────────────
112
+ @app.get("/")
113
+ def root():
114
+ return {"status": "ok", "message": "Audio Classifier is running"}
115
+
116
+ @app.get("/health")
117
+ def health():
118
+ return {"status": "healthy", "model": "CrossAttnModel", "classes": CLASSES}