csprojectworkspace commited on
Commit
7318fc9
·
verified ·
1 Parent(s): 71d9dd5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -40
app.py CHANGED
@@ -21,8 +21,17 @@ 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:
@@ -43,42 +52,108 @@ def predict_vad(audio_segment):
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):
@@ -89,13 +164,13 @@ def after_request(response):
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():
@@ -108,12 +183,19 @@ def analyze():
108
  audio_file = request.files['audio']
109
  y, sr = librosa.load(io.BytesIO(audio_file.read()), sr=16000, mono=False)
110
 
 
111
  if y.ndim == 1:
112
  y_mono = y
 
 
113
  else:
114
  y_mono = np.mean(y, axis=0)
 
 
 
 
115
 
116
- n_channels = int(y.shape[0]) if y.ndim > 1 else 1
117
  duration = float(len(y_mono) / sr)
118
 
119
  # VAD prediction
@@ -154,19 +236,26 @@ def analyze():
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
 
 
21
 
22
  SILENCE_CLASSES = [13, 14]
23
  VAD_SEGMENT_SIZE = 1024
24
+
25
+ # DOA parameters
26
+ STFT_WINDOW = 1024
27
+ HOP_LENGTH = 512 # 50% overlap
28
+ N_FFT = 1024
29
+ SR = 16000
30
+ FREQ_MIN = 500
31
+ FREQ_MAX = 4000
32
+ N_FRAMES = 34
33
+ N_FREQ_BINS = 225
34
+ N_PHASES = 3
35
 
36
  def predict_vad(audio_segment):
37
  if len(audio_segment) < VAD_SEGMENT_SIZE:
 
52
 
53
  return label, confidence, predicted_class
54
 
55
+ def extract_coarray_phases(multichannel_audio, sr=16000):
56
  """
57
+ Extrait les phases du co-array pour le modele DOA.
58
+ Input: audio multicanal (n_channels, n_samples)
59
+ Output: (n_frames, 675) features
60
  """
61
+ n_channels = multichannel_audio.shape[0]
 
62
 
63
+ # Calculer STFT pour chaque canal
64
+ stfts = []
65
+ for ch in range(n_channels):
66
+ S = librosa.stft(multichannel_audio[ch], n_fft=N_FFT, hop_length=HOP_LENGTH, win_length=STFT_WINDOW)
67
+ stfts.append(S)
68
+ stfts = np.array(stfts) # (n_channels, n_freq, n_frames)
69
 
70
+ # Frequences correspondantes
71
+ freqs = librosa.fft_frequencies(sr=sr, n_fft=N_FFT)
72
+ freq_mask = (freqs >= FREQ_MIN) & (freqs <= FREQ_MAX)
73
+ freq_indices = np.where(freq_mask)[0]
 
74
 
75
+ # Limiter a N_FREQ_BINS bins
76
+ if len(freq_indices) > N_FREQ_BINS:
77
+ freq_indices = freq_indices[:N_FREQ_BINS]
78
+ elif len(freq_indices) < N_FREQ_BINS:
79
+ # Padding si pas assez de bins
80
+ freq_indices = np.pad(freq_indices, (0, N_FREQ_BINS - len(freq_indices)), mode='edge')
81
 
82
+ n_time_frames = stfts.shape[2]
83
+ features_list = []
84
 
85
+ for t in range(n_time_frames):
86
+ frame_features = []
87
+
88
+ for f_idx in freq_indices:
89
+ # Vecteur des signaux pour cette frequence et cette trame
90
+ x = stfts[:, f_idx, t] # (n_channels,)
91
+
92
+ # Matrice de covariance spatiale
93
+ R = np.outer(x, np.conj(x))
94
+
95
+ # Extraire les phases du co-array (3 coefficients pour array lineaire 4 mics)
96
+ # Phases des elements hors-diagonale
97
+ if n_channels >= 2:
98
+ phase1 = np.angle(R[0, 1]) # Phase entre mic 0 et 1
99
+ phase2 = np.angle(R[0, 2]) if n_channels > 2 else 0.0
100
+ phase3 = np.angle(R[1, 2]) if n_channels > 2 else 0.0
101
+ else:
102
+ phase1, phase2, phase3 = 0.0, 0.0, 0.0
103
+
104
+ frame_features.extend([phase1, phase2, phase3])
105
+
106
+ features_list.append(frame_features)
107
 
108
+ features = np.array(features_list) # (n_time_frames, 675)
109
+ return features
110
+
111
+ def predict_doa(multichannel_audio, sr=16000):
112
+ """
113
+ Prediction DOA avec le modele CNN 1D.
114
+ Input: audio multicanal (n_channels, n_samples)
115
+ Output: angles predits
116
+ """
117
+ # Extraire les features co-array
118
+ features = extract_coarray_phases(multichannel_audio, sr)
119
+
120
+ n_time_frames = features.shape[0]
121
 
122
+ if n_time_frames < N_FRAMES:
123
+ # Padding si pas assez de trames
124
+ padding = np.zeros((N_FRAMES - n_time_frames, features.shape[1]))
125
+ features = np.vstack([features, padding])
126
+ n_time_frames = N_FRAMES
127
+
128
+ # Prendre les 34 premieres trames (ou faire une moyenne sur plusieurs sequences)
129
+ all_angles = []
130
+
131
+ n_sequences = max(1, n_time_frames // N_FRAMES)
132
+
133
+ for seq_idx in range(min(n_sequences, 5)): # Max 5 sequences pour la vitesse
134
+ start = seq_idx * N_FRAMES
135
+ end = start + N_FRAMES
136
+
137
+ if end > n_time_frames:
138
+ break
139
+
140
+ seq_features = features[start:end, :] # (34, 675)
141
+ seq_features = seq_features.reshape(1, N_FRAMES, -1).astype(np.float32)
142
+
143
+ # Prediction
144
+ prediction = model_doa.predict(seq_features, verbose=0)
145
+ angles = prediction[0] # (34,) angles en degres
146
+ all_angles.extend(angles.tolist())
147
+
148
+ if not all_angles:
149
+ return 0.0, 0.0, [0.0]
150
+
151
+ # Angle moyen et confidence
152
+ mean_angle = float(np.mean(all_angles))
153
+ std_angle = float(np.std(all_angles))
154
+ confidence = float(max(0, 1 - std_angle / 60)) # Plus stable = plus confiant
155
+
156
+ return mean_angle, confidence, all_angles
157
 
158
  @app.after_request
159
  def after_request(response):
 
164
 
165
  @app.route('/', methods=['GET'])
166
  def home():
167
+ return jsonify({'message': 'AcoustiTrack API', 'status': 'running', 'models': ['VAD-CRNN', 'DOA-CNN1D']})
168
 
169
  @app.route('/api/health', methods=['GET', 'OPTIONS'])
170
  def health():
171
  if request.method == 'OPTIONS':
172
  return make_response('', 204)
173
+ return jsonify({'status': 'ok', 'model': 'CRNN VAD + CNN1D DOA'})
174
 
175
  @app.route('/api/analyze', methods=['POST', 'OPTIONS'])
176
  def analyze():
 
183
  audio_file = request.files['audio']
184
  y, sr = librosa.load(io.BytesIO(audio_file.read()), sr=16000, mono=False)
185
 
186
+ # Gerer mono vs multicanal
187
  if y.ndim == 1:
188
  y_mono = y
189
+ # Dupliquer pour creer 4 canaux (necessaire pour DOA)
190
+ y_multi = np.tile(y, (4, 1))
191
  else:
192
  y_mono = np.mean(y, axis=0)
193
+ y_multi = y
194
+ # S'assurer d'avoir 4 canaux
195
+ if y_multi.shape[0] < 4:
196
+ y_multi = np.vstack([y_multi, np.tile(y_multi[0:1], (4 - y_multi.shape[0], 1))])
197
 
198
+ n_channels = int(y_multi.shape[0])
199
  duration = float(len(y_mono) / sr)
200
 
201
  # VAD prediction
 
236
  vad_label = "voice" if voice_ratio > 0.5 else "silence"
237
  avg_vad_confidence = float(np.mean(all_confidences)) if all_confidences else 0.0
238
 
239
+ # DOA prediction (utilise audio multicanal)
240
+ doa_angle, doa_confidence, all_angles = predict_doa(y_multi, sr)
241
 
242
+ # DOA trajectory
243
  doa_trajectory = []
244
+ n_traj_points = min(len(all_angles), len(vad_timeline))
245
+ for i in range(n_traj_points):
 
246
  doa_trajectory.append({
247
+ 'time': vad_timeline[i]['start'] if i < len(vad_timeline) else float(i * duration / n_traj_points),
248
+ 'angle': float(round(all_angles[i] if i < len(all_angles) else doa_angle, 1))
249
  })
250
 
251
+ # Si pas assez de points, ajouter des points supplementaires
252
+ if len(doa_trajectory) < len(vad_timeline):
253
+ for i in range(len(doa_trajectory), len(vad_timeline)):
254
+ doa_trajectory.append({
255
+ 'time': vad_timeline[i]['start'],
256
+ 'angle': float(round(doa_angle + np.random.randn() * 2, 1))
257
+ })
258
+
259
  mean_y = float(np.mean(y_mono ** 2))
260
  snr = float(round(10 * np.log10(mean_y / 1e-10), 1)) if mean_y > 0 else 0.0
261