Jazzcharles commited on
Commit
dbfa08c
·
0 Parent(s):

initial deploy

Browse files
app.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+ from huggingface_hub import HfApi
6
+ import time
7
+ HF_DATASET_REPO = "Jazzcharles/audioverse_for_annotation"
8
+ SYNC_INTERVAL = 300 # 秒,Space 环境建议 60~300
9
+
10
+ # os.environ["GRADIO_TEMP_DIR"] = "/home/jilan_xu/qwen/assets/gradio_temp"
11
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
12
+ TMP_DIR = os.path.join(BASE_DIR, "gradio_tmp")
13
+ os.makedirs(TMP_DIR, exist_ok=True)
14
+ os.environ["GRADIO_TEMP_DIR"] = TMP_DIR
15
+
16
+ DATA_PATH = "data/samples_for_annotation_with_urls.json"
17
+ ASSIGN_PATH = "data/assignments.json"
18
+ RESULT_PATH = "results/results.jsonl"
19
+
20
+ os.makedirs("results", exist_ok=True)
21
+
22
+
23
+ def pull_results_from_hf():
24
+ """
25
+ Download results.jsonl from HF dataset repo to local RESULT_PATH.
26
+ If download fails, keep local file untouched.
27
+ """
28
+ try:
29
+ os.makedirs(os.path.dirname(RESULT_PATH), exist_ok=True)
30
+
31
+ api.download_file(
32
+ repo_id=HF_DATASET_REPO,
33
+ repo_type="dataset",
34
+ filename="results.jsonl",
35
+ local_dir=os.path.dirname(RESULT_PATH),
36
+ local_dir_use_symlinks=False,
37
+ )
38
+ print("[INIT] Pulled results.jsonl from HF dataset.")
39
+ except Exception as e:
40
+ print("[INIT] No remote results.jsonl or pull failed:", e)
41
+
42
+
43
+ # ---- pull latest results from HF dataset ----
44
+ pull_results_from_hf()
45
+
46
+ with open(DATA_PATH, "r") as f:
47
+ SAMPLES = {x["id"]: x for x in json.load(f)}
48
+
49
+ with open(ASSIGN_PATH, "r") as f:
50
+ ASSIGN = json.load(f)
51
+
52
+
53
+ # ------------------------
54
+ # Utilities
55
+ # ------------------------
56
+ def get_user_samples(user):
57
+ return ASSIGN.get(user, [])
58
+
59
+
60
+ def save_result(record):
61
+ with open(RESULT_PATH, "a") as f:
62
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
63
+
64
+ def load_existing_results():
65
+ if not os.path.exists(RESULT_PATH):
66
+ return []
67
+
68
+ records = []
69
+ with open(RESULT_PATH, "r", encoding="utf-8") as f:
70
+ for line in f:
71
+ try:
72
+ records.append(json.loads(line))
73
+ except:
74
+ pass
75
+ return records
76
+
77
+
78
+ def get_user_done_ids(user):
79
+ records = load_existing_results()
80
+ done = {}
81
+ for r in records:
82
+ if r["annotator"] == user:
83
+ done[r["sample_id"]] = r # 后写的覆盖前面的
84
+ return done # {sample_id: last_record}
85
+
86
+ # ------------------------
87
+ # State
88
+ # ------------------------
89
+ def init_state(user):
90
+ sample_ids = get_user_samples(user)
91
+
92
+ done_map = get_user_done_ids(user)
93
+ done_ids = set(done_map.keys())
94
+
95
+ # 只保留未完成的 sample
96
+ pending_ids = [sid for sid in sample_ids if sid not in done_ids]
97
+
98
+ return {
99
+ "user": user,
100
+ "sample_ids": sample_ids,
101
+ "pending_ids": pending_ids,
102
+ "done_map": done_map,
103
+ "idx": 0
104
+ }
105
+
106
+
107
+ api = HfApi()
108
+ _last_sync_time = 0
109
+ def sync_results_to_hf(force=False):
110
+ global _last_sync_time
111
+
112
+ if not os.path.exists(RESULT_PATH):
113
+ return
114
+
115
+ now = time.time()
116
+ if not force and now - _last_sync_time < SYNC_INTERVAL:
117
+ return
118
+
119
+ try:
120
+ api.upload_file(
121
+ path_or_fileobj=RESULT_PATH,
122
+ path_in_repo="results.jsonl",
123
+ repo_id=HF_DATASET_REPO,
124
+ repo_type="dataset",
125
+ commit_message=f"Sync results at {datetime.utcnow().isoformat()}",
126
+ )
127
+ _last_sync_time = now
128
+ print("[SYNC] results.jsonl synced to HF dataset.")
129
+ except Exception as e:
130
+ print("[SYNC ERROR]", e)
131
+
132
+
133
+ # ------------------------
134
+ # Load sample
135
+ # ------------------------
136
+ def load_sample(state):
137
+ if state["idx"] >= len(state["pending_ids"]):
138
+ return None, None, None, None, "All pending tasks completed."
139
+
140
+ sid = state["pending_ids"][state["idx"]]
141
+ sample = SAMPLES[sid]
142
+
143
+ return (
144
+ sample["audio_url"],
145
+ sample["captions"]["long"],
146
+ sample["captions"]["short"],
147
+ sample["captions"]["tag"],
148
+ f"Pending {state['idx']+1}/{len(state['pending_ids'])} (ID={sid})"
149
+ )
150
+
151
+
152
+
153
+
154
+ # ------------------------
155
+ # Submit
156
+ # ------------------------
157
+ def submit(state, long_score, short_score, tag_score):
158
+ sid = state["pending_ids"][state["idx"]]
159
+
160
+ record = {
161
+ "timestamp": datetime.utcnow().isoformat(),
162
+ "annotator": state["user"],
163
+ "sample_id": sid,
164
+ "scores": {
165
+ "long": long_score,
166
+ "short": short_score,
167
+ "tag": tag_score
168
+ }
169
+ }
170
+ save_result(record)
171
+
172
+ # >>> 新增:尝试同步 <<<
173
+ sync_results_to_hf()
174
+
175
+ state["idx"] += 1
176
+ return state
177
+
178
+
179
+
180
+
181
+ # ------------------------
182
+ # UI
183
+ # ------------------------
184
+ with gr.Blocks(title="Audio-Caption Matching Annotation") as demo:
185
+
186
+ gr.Markdown("# Audio–Caption Matching Annotation")
187
+
188
+ with gr.Row():
189
+ user_input = gr.Textbox(label="Annotator ID", placeholder="e.g. annotator_1")
190
+ start_btn = gr.Button("Start")
191
+
192
+ sync_btn = gr.Button("Finish & Sync results to HF")
193
+ sync_status = gr.Markdown()
194
+
195
+ state = gr.State()
196
+
197
+ status = gr.Markdown()
198
+
199
+ audio = gr.Audio(label="Audio", type="filepath")
200
+
201
+ with gr.Column():
202
+ # ---------- LONG ----------
203
+ gr.Markdown("## Long Caption")
204
+ gr.Markdown(
205
+ """
206
+ **Criteria**
207
+ 1. **Event accuracy**: Are the sound events in the caption actually present in the audio?
208
+ 2. **Completeness**: Does the caption miss any major audible events?
209
+ 3. **Temporal consistency**: Does the sequence of events match the audio timeline?
210
+ 4. **Acoustic detail**: Does the caption correctly reflect loudness, duration, tone, speed, environment?
211
+ """
212
+ )
213
+ long_caption = gr.Textbox(label="Caption (Long)", interactive=False)
214
+ long_score = gr.Radio(
215
+ choices=[str(i) for i in range(1, 11)],
216
+ label="Overall Score (1–10)",
217
+ value=None
218
+ )
219
+
220
+ # ---------- SHORT ----------
221
+ gr.Markdown("## Short Caption")
222
+ gr.Markdown(
223
+ """
224
+ **Criteria**
225
+ 1. **Event accuracy**: Are the sound events in the caption actually present in the audio?
226
+ 2. **Completeness**: Does the caption miss any major audible events?
227
+ """
228
+ )
229
+ short_caption = gr.Textbox(label="Caption (Short)", interactive=False)
230
+ short_score = gr.Radio(
231
+ choices=[str(i) for i in range(1, 11)],
232
+ label="Overall Score (1–10)",
233
+ value=None
234
+ )
235
+
236
+ # ---------- TAG ----------
237
+ gr.Markdown("## Tag")
238
+ gr.Markdown(
239
+ """
240
+ **Criteria**
241
+ 1. **Event accuracy**: Are the sound events in the tags actually present in the audio?
242
+ 2. **Completeness**: Does the tags miss any major audible events?
243
+ """
244
+ )
245
+ tag_caption = gr.Textbox(label="Caption (Tag)", interactive=False)
246
+ tag_score = gr.Radio(
247
+ choices=[str(i) for i in range(1, 11)],
248
+ label="Overall Score (1–10)",
249
+ value=None
250
+ )
251
+
252
+ submit_btn = gr.Button("Submit & Next")
253
+
254
+ # ------------------------
255
+ # Callbacks
256
+ # ------------------------
257
+ def on_start(user):
258
+ st = init_state(user)
259
+
260
+ # pending sample
261
+ audio_url, long_c, short_c, tag_c, msg = load_sample(st)
262
+
263
+ # 已完成样本列表
264
+ done_ids = sorted(st["done_map"].keys())
265
+ dropdown_choices = [str(sid) for sid in done_ids]
266
+
267
+ return (
268
+ st,
269
+ audio_url,
270
+ long_c,
271
+ short_c,
272
+ tag_c,
273
+ msg,
274
+ dropdown_choices
275
+ )
276
+
277
+
278
+ start_btn.click(
279
+ on_start,
280
+ inputs=[user_input],
281
+ outputs=[state, audio, long_caption, short_caption, tag_caption, status]
282
+ )
283
+
284
+
285
+ def on_submit(st, l, s, t):
286
+ if l is None or s is None or t is None:
287
+ return st, None, None, None, "Please score all captions before submitting."
288
+
289
+ st = submit(st, l, s, t)
290
+ audio_url, long_c, short_c, tag_c, msg = load_sample(st)
291
+
292
+ # 注意:最后三个 None 是清空评分
293
+ return (
294
+ st,
295
+ audio_url,
296
+ long_c,
297
+ short_c,
298
+ tag_c,
299
+ msg,
300
+ None, # long_score reset
301
+ None, # short_score reset
302
+ None # tag_score reset
303
+ )
304
+
305
+
306
+ submit_btn.click(
307
+ on_submit,
308
+ inputs=[state, long_score, short_score, tag_score],
309
+ outputs=[
310
+ state,
311
+ audio,
312
+ long_caption,
313
+ short_caption,
314
+ tag_caption,
315
+ status,
316
+ long_score,
317
+ short_score,
318
+ tag_score
319
+ ]
320
+ )
321
+
322
+
323
+ demo.launch()
data/assignments.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "annotator_1": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200],
3
+ "annotator_2": [201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400],
4
+ "annotator_3": [401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500]
5
+ }
6
+
data/samples_for_annotation_with_urls.json ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.0
2
+ huggingface_hub>=0.20
results/results.jsonl ADDED
@@ -0,0 +1 @@
 
 
1
+ {"timestamp": "2026-01-14T15:03:44.808170", "annotator": "annotator_1", "sample_id": 1, "scores": {"long": "10", "short": "9", "tag": "9"}}