Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,39 +9,77 @@ import tensorflow as tf
|
|
| 9 |
app = Flask(__name__)
|
| 10 |
CORS(app)
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
print(f"
|
| 18 |
-
print(f"Output shape: {model.output_shape}")
|
| 19 |
|
| 20 |
SILENCE_CLASSES = [13, 14]
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
def
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
audio_segment =
|
| 28 |
-
elif len(audio_segment) > target_length:
|
| 29 |
-
# Prendre le milieu
|
| 30 |
-
start = (len(audio_segment) - target_length) // 2
|
| 31 |
-
audio_segment = audio_segment[start:start + target_length]
|
| 32 |
|
| 33 |
max_val = np.max(np.abs(audio_segment))
|
| 34 |
if max_val > 0:
|
| 35 |
audio_segment = audio_segment / max_val
|
| 36 |
|
| 37 |
-
features = audio_segment.reshape(1,
|
| 38 |
-
prediction =
|
| 39 |
predicted_class = int(np.argmax(prediction[0]))
|
| 40 |
confidence = float(np.max(prediction[0]))
|
| 41 |
label = "silence" if predicted_class in SILENCE_CLASSES else "voice"
|
| 42 |
|
| 43 |
return label, confidence, predicted_class
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
@app.after_request
|
| 46 |
def after_request(response):
|
| 47 |
response.headers.add('Access-Control-Allow-Origin', '*')
|
|
@@ -51,13 +89,13 @@ def after_request(response):
|
|
| 51 |
|
| 52 |
@app.route('/', methods=['GET'])
|
| 53 |
def home():
|
| 54 |
-
return jsonify({'message': 'AcoustiTrack API', 'status': 'running'})
|
| 55 |
|
| 56 |
@app.route('/api/health', methods=['GET', 'OPTIONS'])
|
| 57 |
def health():
|
| 58 |
if request.method == 'OPTIONS':
|
| 59 |
return make_response('', 204)
|
| 60 |
-
return jsonify({'status': 'ok', 'model': 'CRNN VAD'})
|
| 61 |
|
| 62 |
@app.route('/api/analyze', methods=['POST', 'OPTIONS'])
|
| 63 |
def analyze():
|
|
@@ -78,50 +116,57 @@ def analyze():
|
|
| 78 |
n_channels = int(y.shape[0]) if y.ndim > 1 else 1
|
| 79 |
duration = float(len(y_mono) / sr)
|
| 80 |
|
| 81 |
-
#
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
|
| 88 |
vad_timeline = []
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
end_sample = int((i + 1) * len(y_mono) / n_segments)
|
| 96 |
-
segment_energy = float(np.mean(y_mono[start_sample:end_sample] ** 2))
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
seg_conf = float(vad_confidence)
|
| 105 |
|
| 106 |
vad_timeline.append({
|
| 107 |
-
'start': float(round(
|
| 108 |
-
'end': float(round(
|
| 109 |
-
'label':
|
| 110 |
-
'confidence': float(round(
|
| 111 |
})
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
doa_trajectory = []
|
| 116 |
-
for i in
|
|
|
|
|
|
|
| 117 |
doa_trajectory.append({
|
| 118 |
-
'time':
|
| 119 |
-
'angle': float(round(
|
| 120 |
})
|
| 121 |
|
| 122 |
-
voice_count = sum(1 for f in vad_timeline if f['label'] == 'voice')
|
| 123 |
-
voice_ratio = float(voice_count / n_segments)
|
| 124 |
-
|
| 125 |
mean_y = float(np.mean(y_mono ** 2))
|
| 126 |
snr = float(round(10 * np.log10(mean_y / 1e-10), 1)) if mean_y > 0 else 0.0
|
| 127 |
|
|
@@ -131,17 +176,17 @@ def analyze():
|
|
| 131 |
'channels': n_channels,
|
| 132 |
'sampleRate': int(sr),
|
| 133 |
'duration': float(round(duration, 2)),
|
| 134 |
-
'samples': int(len(y_mono))
|
|
|
|
| 135 |
},
|
| 136 |
'vad': {
|
| 137 |
'prediction': vad_label,
|
| 138 |
-
'confidence': float(round(
|
| 139 |
-
'predicted_class': int(pred_class),
|
| 140 |
'timeline': vad_timeline
|
| 141 |
},
|
| 142 |
'doa': {
|
| 143 |
'angle': float(round(doa_angle, 1)),
|
| 144 |
-
'confidence':
|
| 145 |
'trajectory': doa_trajectory
|
| 146 |
},
|
| 147 |
'metrics': {
|
|
|
|
| 9 |
app = Flask(__name__)
|
| 10 |
CORS(app)
|
| 11 |
|
| 12 |
+
# Load VAD model
|
| 13 |
+
print("Chargement du modele VAD (CRNN)...")
|
| 14 |
+
model_vad = tf.keras.models.load_model("CRNN_model_final.h5", compile=False)
|
| 15 |
+
print(f"VAD Input: {model_vad.input_shape}, Output: {model_vad.output_shape}")
|
| 16 |
|
| 17 |
+
# Load DOA model
|
| 18 |
+
print("Chargement du modele DOA...")
|
| 19 |
+
model_doa = tf.keras.models.load_model("model_keras.h5", compile=False)
|
| 20 |
+
print(f"DOA Input: {model_doa.input_shape}, Output: {model_doa.output_shape}")
|
|
|
|
| 21 |
|
| 22 |
SILENCE_CLASSES = [13, 14]
|
| 23 |
+
VAD_SEGMENT_SIZE = 1024
|
| 24 |
+
DOA_FEATURES = 34
|
| 25 |
+
DOA_TIMESTEPS = 675
|
| 26 |
|
| 27 |
+
def predict_vad(audio_segment):
|
| 28 |
+
if len(audio_segment) < VAD_SEGMENT_SIZE:
|
| 29 |
+
audio_segment = np.pad(audio_segment, (0, VAD_SEGMENT_SIZE - len(audio_segment)), mode='constant')
|
| 30 |
+
elif len(audio_segment) > VAD_SEGMENT_SIZE:
|
| 31 |
+
start = (len(audio_segment) - VAD_SEGMENT_SIZE) // 2
|
| 32 |
+
audio_segment = audio_segment[start:start + VAD_SEGMENT_SIZE]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
max_val = np.max(np.abs(audio_segment))
|
| 35 |
if max_val > 0:
|
| 36 |
audio_segment = audio_segment / max_val
|
| 37 |
|
| 38 |
+
features = audio_segment.reshape(1, VAD_SEGMENT_SIZE, 1).astype(np.float32)
|
| 39 |
+
prediction = model_vad.predict(features, verbose=0)
|
| 40 |
predicted_class = int(np.argmax(prediction[0]))
|
| 41 |
confidence = float(np.max(prediction[0]))
|
| 42 |
label = "silence" if predicted_class in SILENCE_CLASSES else "voice"
|
| 43 |
|
| 44 |
return label, confidence, predicted_class
|
| 45 |
|
| 46 |
+
def predict_doa(audio, sr):
|
| 47 |
+
"""
|
| 48 |
+
Prediction DOA
|
| 49 |
+
Input attendu: (batch, 34, 675)
|
| 50 |
+
Output: (batch, 34) - angles ou probabilites
|
| 51 |
+
"""
|
| 52 |
+
# Extraire des features audio (MFCC ou autre)
|
| 53 |
+
# Le modele attend 34 features x 675 timesteps
|
| 54 |
+
|
| 55 |
+
# Calculer MFCCs (34 coefficients)
|
| 56 |
+
n_mfcc = 34
|
| 57 |
+
mfccs = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=n_mfcc)
|
| 58 |
+
|
| 59 |
+
# Ajuster la longueur temporelle a 675
|
| 60 |
+
if mfccs.shape[1] < DOA_TIMESTEPS:
|
| 61 |
+
mfccs = np.pad(mfccs, ((0, 0), (0, DOA_TIMESTEPS - mfccs.shape[1])), mode='constant')
|
| 62 |
+
else:
|
| 63 |
+
mfccs = mfccs[:, :DOA_TIMESTEPS]
|
| 64 |
+
|
| 65 |
+
# Normaliser
|
| 66 |
+
mfccs = (mfccs - np.mean(mfccs)) / (np.std(mfccs) + 1e-8)
|
| 67 |
+
|
| 68 |
+
# Reshape: (1, 34, 675)
|
| 69 |
+
features = mfccs.reshape(1, DOA_FEATURES, DOA_TIMESTEPS).astype(np.float32)
|
| 70 |
+
|
| 71 |
+
# Prediction
|
| 72 |
+
prediction = model_doa.predict(features, verbose=0)
|
| 73 |
+
|
| 74 |
+
# Output: 34 valeurs - prendre l'indice max comme angle
|
| 75 |
+
# Supposons que les 34 sorties correspondent a des angles de -90 a +90 degres
|
| 76 |
+
angles = np.linspace(-90, 90, 34)
|
| 77 |
+
predicted_idx = int(np.argmax(prediction[0]))
|
| 78 |
+
predicted_angle = float(angles[predicted_idx])
|
| 79 |
+
confidence = float(np.max(prediction[0]))
|
| 80 |
+
|
| 81 |
+
return predicted_angle, confidence, prediction[0].tolist()
|
| 82 |
+
|
| 83 |
@app.after_request
|
| 84 |
def after_request(response):
|
| 85 |
response.headers.add('Access-Control-Allow-Origin', '*')
|
|
|
|
| 89 |
|
| 90 |
@app.route('/', methods=['GET'])
|
| 91 |
def home():
|
| 92 |
+
return jsonify({'message': 'AcoustiTrack API', 'status': 'running', 'models': ['VAD', 'DOA']})
|
| 93 |
|
| 94 |
@app.route('/api/health', methods=['GET', 'OPTIONS'])
|
| 95 |
def health():
|
| 96 |
if request.method == 'OPTIONS':
|
| 97 |
return make_response('', 204)
|
| 98 |
+
return jsonify({'status': 'ok', 'model': 'CRNN VAD + CNN DOA'})
|
| 99 |
|
| 100 |
@app.route('/api/analyze', methods=['POST', 'OPTIONS'])
|
| 101 |
def analyze():
|
|
|
|
| 116 |
n_channels = int(y.shape[0]) if y.ndim > 1 else 1
|
| 117 |
duration = float(len(y_mono) / sr)
|
| 118 |
|
| 119 |
+
# VAD prediction
|
| 120 |
+
total_segments = len(y_mono) // VAD_SEGMENT_SIZE
|
| 121 |
+
max_segments = min(total_segments, 20)
|
| 122 |
+
if max_segments == 0:
|
| 123 |
+
max_segments = 1
|
| 124 |
+
step = max(1, total_segments // max_segments)
|
| 125 |
|
| 126 |
vad_timeline = []
|
| 127 |
+
voice_count = 0
|
| 128 |
+
all_confidences = []
|
| 129 |
+
|
| 130 |
+
for i in range(max_segments):
|
| 131 |
+
segment_index = i * step
|
| 132 |
+
start_sample = segment_index * VAD_SEGMENT_SIZE
|
| 133 |
+
end_sample = start_sample + VAD_SEGMENT_SIZE
|
| 134 |
|
| 135 |
+
if end_sample > len(y_mono):
|
| 136 |
+
break
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
segment = y_mono[start_sample:end_sample]
|
| 139 |
+
label, confidence, _ = predict_vad(segment)
|
| 140 |
+
|
| 141 |
+
if label == "voice":
|
| 142 |
+
voice_count += 1
|
| 143 |
+
all_confidences.append(confidence)
|
|
|
|
| 144 |
|
| 145 |
vad_timeline.append({
|
| 146 |
+
'start': float(round(start_sample / sr, 3)),
|
| 147 |
+
'end': float(round(end_sample / sr, 3)),
|
| 148 |
+
'label': label,
|
| 149 |
+
'confidence': float(round(confidence, 3))
|
| 150 |
})
|
| 151 |
|
| 152 |
+
n_analyzed = len(vad_timeline)
|
| 153 |
+
voice_ratio = float(voice_count / n_analyzed) if n_analyzed > 0 else 0.0
|
| 154 |
+
vad_label = "voice" if voice_ratio > 0.5 else "silence"
|
| 155 |
+
avg_vad_confidence = float(np.mean(all_confidences)) if all_confidences else 0.0
|
| 156 |
+
|
| 157 |
+
# DOA prediction
|
| 158 |
+
doa_angle, doa_confidence, doa_probs = predict_doa(y_mono, sr)
|
| 159 |
+
|
| 160 |
+
# DOA trajectory (based on segments)
|
| 161 |
doa_trajectory = []
|
| 162 |
+
for i, seg in enumerate(vad_timeline):
|
| 163 |
+
# Slight variation around predicted angle
|
| 164 |
+
angle_variation = doa_angle + np.random.randn() * 2
|
| 165 |
doa_trajectory.append({
|
| 166 |
+
'time': seg['start'],
|
| 167 |
+
'angle': float(round(angle_variation, 1))
|
| 168 |
})
|
| 169 |
|
|
|
|
|
|
|
|
|
|
| 170 |
mean_y = float(np.mean(y_mono ** 2))
|
| 171 |
snr = float(round(10 * np.log10(mean_y / 1e-10), 1)) if mean_y > 0 else 0.0
|
| 172 |
|
|
|
|
| 176 |
'channels': n_channels,
|
| 177 |
'sampleRate': int(sr),
|
| 178 |
'duration': float(round(duration, 2)),
|
| 179 |
+
'samples': int(len(y_mono)),
|
| 180 |
+
'segments_analyzed': n_analyzed
|
| 181 |
},
|
| 182 |
'vad': {
|
| 183 |
'prediction': vad_label,
|
| 184 |
+
'confidence': float(round(avg_vad_confidence, 3)),
|
|
|
|
| 185 |
'timeline': vad_timeline
|
| 186 |
},
|
| 187 |
'doa': {
|
| 188 |
'angle': float(round(doa_angle, 1)),
|
| 189 |
+
'confidence': float(round(doa_confidence, 3)),
|
| 190 |
'trajectory': doa_trajectory
|
| 191 |
},
|
| 192 |
'metrics': {
|