LaurentTRIPIED commited on
Commit
99f6e51
·
1 Parent(s): 7f81391

Synthèse filtrée ≥0,5 % et barres horizontales

Browse files
Files changed (1) hide show
  1. app.py +86 -57
app.py CHANGED
@@ -2,23 +2,35 @@
2
  La Fréquence du Vivant – Bio acoustic découverte
3
  Comédie des Mondes Hybrides · Gaspard Boréal
4
  ------------------------------------------------
5
- • Upload .wav/.mp3 (≤ 30 s) OU colle un lien http(s) (Dictaphone iCloud, Dropbox…)
6
- • Prédit les 3 tags AudioSet (AST 527) + spectrogramme
 
7
  • Boutons : « Nouveau fichier » & « Voir tags (condensé / complet) »
8
  """
9
 
10
  # ---------------------------- Imports ---------------------------------
11
- import os, pathlib, tempfile, requests, matplotlib.pyplot as plt
12
  import torch, torchaudio, librosa, gradio as gr
13
  from transformers import pipeline
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  # ---------------------------- 1. Modèle -------------------------------
16
- clf = pipeline(
17
- task="audio-classification",
18
- model="MIT/ast-finetuned-audioset-10-10-0.4593",
19
- top_k=3,
20
- device=0 if torch.cuda.is_available() else -1,
21
- )
22
 
23
  # ---------------------------- 2. Utils audio --------------------------
24
  def load_audio(path, target_sr=16000, max_sec=30):
@@ -37,89 +49,106 @@ def load_audio(path, target_sr=16000, max_sec=30):
37
  return wav, sr
38
 
39
  def top_k_dict(d, k=5):
40
- return {k_: v_ for k_, v_ in sorted(d.items(), key=lambda x: x[1], reverse=True)[:k] if v_ > 0}
 
41
 
42
  # ---------------------------- 3. Analyse ------------------------------
 
 
43
  def analyse(audio_path, expanded):
44
  wav, sr = load_audio(audio_path)
45
- res = clf(wav.numpy().squeeze(), sampling_rate=sr)
46
- full = {d["label"]: round(float(d["score"]), 4) for d in res}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  disp = full if expanded else top_k_dict(full, 5)
48
- fig, ax = plt.subplots(figsize=(6, 2)); ax.specgram(wav.numpy()[0], Fs=sr, NFFT=1024, noverlap=512)
 
 
 
 
 
 
 
 
49
  ax.set_axis_off(); plt.tight_layout()
50
- return disp, fig, full
51
-
52
- def analyse_link(link, expanded):
53
- if not link.startswith(("http://", "https://")):
54
- raise gr.Error("Colle un lien commençant par http:// ou https://")
55
- try:
56
- r = requests.get(link, timeout=10); r.raise_for_status()
57
- except Exception as e:
58
- raise gr.Error(f"Téléchargement impossible : {e}")
59
- with tempfile.NamedTemporaryFile(delete=False, suffix=".tmp") as tmp:
60
- tmp.write(r.content); tmp_path = tmp.name
61
- try:
62
- return analyse(tmp_path, expanded)
63
- finally:
64
- os.remove(tmp_path)
65
 
66
  # ---------------------------- 4. Interface ----------------------------
67
  with gr.Blocks(title="La Fréquence du Vivant – Bio acoustic découverte") as demo:
68
 
69
- gr.Markdown("# La Fréquence du Vivant – Bio acoustic découverte")
70
- gr.Markdown("### Outils pour marche techno-sensible entre vivant, humain et machine")
71
 
72
  expanded = gr.State(False)
73
  full_tags = gr.State({})
 
74
 
75
- # Champ lien + bouton analysé uniquement si URL http(s)
76
- with gr.Row():
77
- url_in = gr.Textbox(lines=1,
78
- placeholder="�� Colle un lien http(s) (Dictaphone iCloud, Dropbox, etc.)",
79
- label="Analyse via lien web")
80
- url_btn = gr.Button("Analyser le lien", size="sm", variant="primary", interactive=False)
81
-
82
- # Upload local
83
  audio_in = gr.Audio(sources=["upload"], type="filepath",
84
- label="🎙️ Ou charge un fichier .wav / .mp3 (≤ 30 s)")
 
 
 
 
 
 
 
85
 
86
- # Tags + boutons
87
  gr.Markdown("**Tags AudioSet**")
88
  with gr.Row():
89
  reset_btn = gr.Button("🔄 Nouveau fichier / Réinitialiser", size="sm")
90
  toggle_btn = gr.Button("Voir tous les tags", size="sm", variant="primary")
 
91
  tags_out = gr.Label()
92
  spec_out = gr.Plot(label="Spectrogramme")
93
 
94
- # --------- Helpers ----------
95
  def _txt(exp): return "Uniquement les principaux tags" if exp else "Voir tous les tags"
96
  def flip(b): return not b
97
- def refresh(exp, full): return _txt(exp), (full if exp else top_k_dict(full, 5))
 
 
98
  def reset_ui():
99
  fig = plt.figure(figsize=(6, 2)); plt.axis("off")
100
- return None, "", {}, fig, {}, False, "Voir tous les tags", gr.update(interactive=False)
101
-
102
- # Activer bouton si URL http(s)
103
- def enable_btn(link): return gr.update(interactive=link.startswith(("http://", "https://")))
104
- url_in.change(enable_btn, url_in, url_btn)
105
 
106
- # Bouton Analyse lien
107
- url_btn.click(analyse_link, [url_in, expanded], [tags_out, spec_out, full_tags])
 
108
 
109
- # Upload local
110
- audio_in.upload(analyse, [audio_in, expanded], [tags_out, spec_out, full_tags])
111
-
112
- # Toggle
113
  toggle_btn.click(flip, expanded, expanded)\
114
- .then(refresh, [expanded, full_tags], [toggle_btn, tags_out])
 
 
115
 
116
- # Reset
117
  reset_btn.click(
118
  reset_ui, None,
119
- [audio_in, url_in, tags_out, spec_out, full_tags, expanded, toggle_btn]
 
 
 
 
 
 
120
  )
121
 
122
- gr.Markdown("<center>2025 : Gaspard Boréal / La comédie des mondes hybrydes</center>")
123
 
124
  # ---------------------------- 5. Run ----------------------------------
125
  if __name__ == "__main__":
 
2
  La Fréquence du Vivant – Bio acoustic découverte
3
  Comédie des Mondes Hybrides · Gaspard Boréal
4
  ------------------------------------------------
5
+ • Upload .wav/.mp3 (≤ 30 s)
6
+ • Prédit les tags AudioSet (AST 527) + spectrogramme
7
+ • Synthèse bio-acoustique : barres horizontales (≥ 0,5 %)
8
  • Boutons : « Nouveau fichier » & « Voir tags (condensé / complet) »
9
  """
10
 
11
  # ---------------------------- Imports ---------------------------------
12
+ import json, pathlib, matplotlib.pyplot as plt, pandas as pd
13
  import torch, torchaudio, librosa, gradio as gr
14
  from transformers import pipeline
15
 
16
+ # ---------------------------- 0. Ontology -----------------------------
17
+ ONTO = json.loads(pathlib.Path(__file__).with_name("ontology.json").read_text())
18
+ by_id = {n["id"]: n for n in ONTO}
19
+ name2id = {n["name"]: n["id"] for n in ONTO}
20
+ parents = {}
21
+ for n in ONTO:
22
+ for c in n.get("child_ids", []):
23
+ parents.setdefault(c, []).append(n["id"])
24
+ def root_parent(mid): # ID racine
25
+ while parents.get(mid):
26
+ mid = parents[mid][0]
27
+ return mid
28
+
29
  # ---------------------------- 1. Modèle -------------------------------
30
+ clf = pipeline("audio-classification",
31
+ model="MIT/ast-finetuned-audioset-10-10-0.4593",
32
+ top_k=None,
33
+ device=0 if torch.cuda.is_available() else -1)
 
 
34
 
35
  # ---------------------------- 2. Utils audio --------------------------
36
  def load_audio(path, target_sr=16000, max_sec=30):
 
49
  return wav, sr
50
 
51
  def top_k_dict(d, k=5):
52
+ return {k_: v_ for k_, v_ in
53
+ sorted(d.items(), key=lambda x: x[1], reverse=True)[:k]}
54
 
55
  # ---------------------------- 3. Analyse ------------------------------
56
+ THRESH = 0.005 # 0,5 % (score >= 0.005)
57
+
58
  def analyse(audio_path, expanded):
59
  wav, sr = load_audio(audio_path)
60
+ res = clf(wav.numpy().squeeze(), sampling_rate=sr)
61
+
62
+ full = {}
63
+ for d in res:
64
+ s = float(d["score"]) / 100 if d["score"] > 1 else float(d["score"])
65
+ if s >= THRESH:
66
+ full[d["label"]] = round(s, 4)
67
+
68
+ # Synthèse racine (max) puis normalisation
69
+ synth_raw = {}
70
+ for label, sc in full.items():
71
+ mid = name2id.get(label)
72
+ if mid:
73
+ root = by_id[root_parent(mid)]["name"]
74
+ synth_raw[root] = max(synth_raw.get(root, 0), sc)
75
+ tot = sum(synth_raw.values()) or 1
76
+ synth_norm = {k: sc / tot for k, sc in synth_raw.items()}
77
+
78
  disp = full if expanded else top_k_dict(full, 5)
79
+
80
+ # DataFrame filtré pour BarPlot (> 0,5 %)
81
+ df = (pd.DataFrame({"Racine": synth_norm.keys(),
82
+ "Pourcent": [round(v * 100, 1) for v in synth_norm.values()]})
83
+ .query("Pourcent >= 0.5")
84
+ .sort_values("Pourcent", ascending=False))
85
+
86
+ fig, ax = plt.subplots(figsize=(6, 2))
87
+ ax.specgram(wav.numpy()[0], Fs=sr, NFFT=1024, noverlap=512)
88
  ax.set_axis_off(); plt.tight_layout()
89
+
90
+ return disp, fig, full, df
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  # ---------------------------- 4. Interface ----------------------------
93
  with gr.Blocks(title="La Fréquence du Vivant – Bio acoustic découverte") as demo:
94
 
95
+ gr.Markdown("# La Fréquence du Vivant")
96
+ gr.Markdown("### Écoute bio-acoustique : marche techno-sensible entre vivant, humain et machine")
97
 
98
  expanded = gr.State(False)
99
  full_tags = gr.State({})
100
+ synth_df = gr.State(pd.DataFrame())
101
 
 
 
 
 
 
 
 
 
102
  audio_in = gr.Audio(sources=["upload"], type="filepath",
103
+ label="🎙️ Charger un fichier .wav / .mp3 (≤ 30 s)")
104
+
105
+ # Synthèse
106
+ gr.Markdown("**Synthèse bio-acoustique (racines)**")
107
+ synth_out = gr.BarPlot(x="Racine", y="Pourcent",
108
+ y_lim=(0, 100),
109
+ height=260,
110
+ title="Répartition par racine (%)")
111
 
112
+ # Détails tags
113
  gr.Markdown("**Tags AudioSet**")
114
  with gr.Row():
115
  reset_btn = gr.Button("🔄 Nouveau fichier / Réinitialiser", size="sm")
116
  toggle_btn = gr.Button("Voir tous les tags", size="sm", variant="primary")
117
+
118
  tags_out = gr.Label()
119
  spec_out = gr.Plot(label="Spectrogramme")
120
 
121
+ # Helpers
122
  def _txt(exp): return "Uniquement les principaux tags" if exp else "Voir tous les tags"
123
  def flip(b): return not b
124
+ def refresh(exp, full, df):
125
+ disp = full if exp else top_k_dict(full, 5)
126
+ return _txt(exp), disp, df
127
  def reset_ui():
128
  fig = plt.figure(figsize=(6, 2)); plt.axis("off")
129
+ return None, {}, fig, pd.DataFrame(), {}, False, "Voir tous les tags"
 
 
 
 
130
 
131
+ audio_in.upload(analyse,
132
+ [audio_in, expanded],
133
+ [tags_out, spec_out, full_tags, synth_df])
134
 
 
 
 
 
135
  toggle_btn.click(flip, expanded, expanded)\
136
+ .then(refresh,
137
+ [expanded, full_tags, synth_df],
138
+ [toggle_btn, tags_out, synth_out])
139
 
 
140
  reset_btn.click(
141
  reset_ui, None,
142
+ [audio_in, tags_out, spec_out,
143
+ synth_out, full_tags, expanded, toggle_btn]
144
+ )
145
+
146
+ synth_df.change(
147
+ lambda d: d.query("Pourcent >= 0.5").sort_values("Pourcent", ascending=False),
148
+ synth_df, synth_out
149
  )
150
 
151
+ gr.Markdown("<center>2025 : Gaspard Boréal / La comédie des mondes hybrides</center>")
152
 
153
  # ---------------------------- 5. Run ----------------------------------
154
  if __name__ == "__main__":