studyOverflow commited on
Commit
e4df8aa
·
verified ·
1 Parent(s): 135e7b6

feat: add skyreels+longcat (pool 4672); per-annotator dedup; global progress bar

Browse files
Files changed (1) hide show
  1. app.py +136 -15
app.py CHANGED
@@ -25,8 +25,8 @@ from huggingface_hub import CommitScheduler, hf_hub_download, hf_hub_url
25
  DATASET_REPO = "studyOverflow/TempMemoryData"
26
  MERGED_JSON_PATH = "MBench-V/merged.json"
27
 
28
- # 6 fully-reorganized models (584 videos each). `skyreels` and `longcat`
29
- # are temporarily excluded until their 0422 runs finish.
30
  MODELS: list[str] = [
31
  "causal_forcing",
32
  "self_forcing",
@@ -34,6 +34,8 @@ MODELS: list[str] = [
34
  "helios",
35
  "longlive",
36
  "memflow",
 
 
37
  ]
38
 
39
  HF_TOKEN = os.environ.get("HF_TOKEN")
@@ -118,6 +120,85 @@ def _append_annotation(record: dict[str, Any]) -> None:
118
  else:
119
  with ANN_FILE.open("a", encoding="utf-8") as f:
120
  f.write(line + "\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
 
123
  # ---------------------------------------------------------------------------
@@ -165,23 +246,50 @@ def _load_item(pool_order: list[int], idx: int) -> tuple[str, str, str]:
165
  def start_session(annotator: str, state: dict):
166
  annotator = (annotator or "").strip()
167
  if not annotator:
168
- return state, "", "⚠️ Please enter a name first.", "", "⚠️ Please enter a name first."
169
- order = list(range(len(POOL)))
 
 
 
 
 
170
  rng = random.Random(f"{annotator}-{int(time.time())}")
171
  rng.shuffle(order)
172
  state = {"annotator": annotator, "order": order, "idx": 0}
 
 
 
 
 
 
 
 
 
 
 
 
173
  video_html, meta, prompt = _load_item(order, 0)
174
- status = f"✅ Logged in as `{annotator}` — {len(order)} items to annotate."
175
- return state, video_html, meta, prompt, status
 
 
 
 
176
 
177
 
178
  def submit_and_next(state: dict, score: float, note: str):
179
  if not state or "order" not in state:
180
- return state, "", "⚠️ Please log in first.", "", 3, "", "⚠️ Not logged in."
 
 
 
181
  order = state["order"]
182
  idx = state["idx"]
183
  if idx >= len(order):
184
- return state, "", "**All done!**", "", 3, "", "No more items."
 
 
 
185
  model, task_id = POOL[order[idx]]
186
  record = {
187
  "timestamp": time.time(),
@@ -196,15 +304,24 @@ def submit_and_next(state: dict, score: float, note: str):
196
  _append_annotation(record)
197
  state["idx"] = idx + 1
198
  video_html, meta, prompt = _load_item(state["order"], state["idx"])
199
- return state, video_html, meta, prompt, 3, "", f"✅ Submitted ({state['idx']}). Next →"
 
 
 
200
 
201
 
202
  def skip_and_next(state: dict):
203
  if not state or "order" not in state:
204
- return state, "", "⚠️ Please log in first.", "", 3, "", "⚠️ Not logged in."
 
 
 
205
  state["idx"] = state["idx"] + 1
206
  video_html, meta, prompt = _load_item(state["order"], state["idx"])
207
- return state, video_html, meta, prompt, 3, "", f"⏭️ Skipped. Position: {state['idx']}"
 
 
 
208
 
209
 
210
  # ---------------------------------------------------------------------------
@@ -220,9 +337,13 @@ with gr.Blocks(title="MBench-V Annotation", theme=gr.themes.Soft()) as demo:
220
  2. Click **Start** — a video will appear below.
221
  3. Give a score (1–5, 5 = best) and optional note; click **Submit & Next**.
222
  4. Submissions auto-sync to the dataset repo every 5 minutes.
 
 
223
  """
224
  )
225
 
 
 
226
  state = gr.State()
227
 
228
  with gr.Row():
@@ -260,25 +381,25 @@ with gr.Blocks(title="MBench-V Annotation", theme=gr.themes.Soft()) as demo:
260
  login_btn.click(
261
  start_session,
262
  inputs=[annotator_in, state],
263
- outputs=[state, video, meta_md, prompt_tb, status_md],
264
  api_name=False,
265
  )
266
  annotator_in.submit(
267
  start_session,
268
  inputs=[annotator_in, state],
269
- outputs=[state, video, meta_md, prompt_tb, status_md],
270
  api_name=False,
271
  )
272
  submit_btn.click(
273
  submit_and_next,
274
  inputs=[state, score, note],
275
- outputs=[state, video, meta_md, prompt_tb, score, note, status_md],
276
  api_name=False,
277
  )
278
  skip_btn.click(
279
  skip_and_next,
280
  inputs=[state],
281
- outputs=[state, video, meta_md, prompt_tb, score, note, status_md],
282
  api_name=False,
283
  )
284
 
 
25
  DATASET_REPO = "studyOverflow/TempMemoryData"
26
  MERGED_JSON_PATH = "MBench-V/merged.json"
27
 
28
+ # 8 fully-reorganized models (584 videos each). All 8 models have complete
29
+ # data as of 2026-05-01.
30
  MODELS: list[str] = [
31
  "causal_forcing",
32
  "self_forcing",
 
34
  "helios",
35
  "longlive",
36
  "memflow",
37
+ "longcat",
38
+ "skyreels",
39
  ]
40
 
41
  HF_TOKEN = os.environ.get("HF_TOKEN")
 
120
  else:
121
  with ANN_FILE.open("a", encoding="utf-8") as f:
122
  f.write(line + "\n")
123
+ # Update in-memory mirror so progress stats react immediately (the
124
+ # committed file only arrives on the dataset every COMMIT_INTERVAL_MIN).
125
+ HISTORICAL_ANNOTATIONS.append(record)
126
+
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Load historical annotations (for dedup + progress stats)
130
+ # ---------------------------------------------------------------------------
131
+
132
+ def _fetch_remote_annotations() -> list[dict[str, Any]]:
133
+ """Download and parse every .jsonl file under `annotations/` on the dataset repo.
134
+
135
+ Returns a list of records. Silently returns [] if the folder does not exist
136
+ or any download fails — annotation UX should never be blocked by this.
137
+ """
138
+ from huggingface_hub import HfApi
139
+ records: list[dict[str, Any]] = []
140
+ try:
141
+ api = HfApi(token=HF_TOKEN)
142
+ files = api.list_repo_files(
143
+ repo_id=DATASET_REPO, repo_type="dataset",
144
+ )
145
+ except Exception as e:
146
+ print(f"[mbench-ann] list_repo_files failed: {e}")
147
+ return records
148
+ jsonls = [p for p in files if p.startswith("annotations/") and p.endswith(".jsonl")]
149
+ print(f"[mbench-ann] found {len(jsonls)} historical annotation files")
150
+ for path in jsonls:
151
+ try:
152
+ local = hf_hub_download(
153
+ repo_id=DATASET_REPO, filename=path,
154
+ repo_type="dataset", token=HF_TOKEN,
155
+ )
156
+ with open(local, encoding="utf-8") as f:
157
+ for line in f:
158
+ line = line.strip()
159
+ if not line:
160
+ continue
161
+ try:
162
+ records.append(json.loads(line))
163
+ except Exception:
164
+ pass
165
+ except Exception as e:
166
+ print(f"[mbench-ann] skip {path}: {e}")
167
+ return records
168
+
169
+
170
+ HISTORICAL_ANNOTATIONS: list[dict[str, Any]] = _fetch_remote_annotations()
171
+ print(f"[mbench-ann] loaded {len(HISTORICAL_ANNOTATIONS)} historical annotation records")
172
+
173
+
174
+ def _global_stats() -> tuple[int, int]:
175
+ """(total_records, unique_(model,task_id)_pairs_covered)."""
176
+ seen: set[tuple[str, str]] = set()
177
+ for r in HISTORICAL_ANNOTATIONS:
178
+ if "model" in r and "task_id" in r:
179
+ seen.add((r["model"], r["task_id"]))
180
+ return len(HISTORICAL_ANNOTATIONS), len(seen)
181
+
182
+
183
+ def _annotator_seen(annotator: str) -> set[tuple[str, str]]:
184
+ """`(model, task_id)` pairs already annotated by this annotator."""
185
+ annotator_l = annotator.strip().lower()
186
+ seen: set[tuple[str, str]] = set()
187
+ for r in HISTORICAL_ANNOTATIONS:
188
+ if (r.get("annotator") or "").strip().lower() == annotator_l:
189
+ seen.add((r.get("model", ""), r.get("task_id", "")))
190
+ return seen
191
+
192
+
193
+ def _global_stats_md() -> str:
194
+ total, unique = _global_stats()
195
+ pool_sz = len(POOL)
196
+ pct = (unique / pool_sz * 100) if pool_sz else 0
197
+ return (
198
+ f"**Dataset progress**: {total} total annotations submitted • "
199
+ f"{unique} / {pool_sz} unique (model, task_id) pairs covered ({pct:.1f}%)"
200
+ )
201
+
202
 
203
 
204
  # ---------------------------------------------------------------------------
 
246
  def start_session(annotator: str, state: dict):
247
  annotator = (annotator or "").strip()
248
  if not annotator:
249
+ return (
250
+ state, "", "⚠️ Please enter a name first.", "",
251
+ "⚠️ Please enter a name first.", _global_stats_md(),
252
+ )
253
+ # Filter out (model, task_id) already annotated by this annotator
254
+ seen = _annotator_seen(annotator)
255
+ order: list[int] = [i for i, (m, t) in enumerate(POOL) if (m, t) not in seen]
256
  rng = random.Random(f"{annotator}-{int(time.time())}")
257
  rng.shuffle(order)
258
  state = {"annotator": annotator, "order": order, "idx": 0}
259
+ if not order:
260
+ status = (
261
+ f"🎉 Welcome back `{annotator}` — you have already annotated every item. Nothing left to do!"
262
+ )
263
+ return (
264
+ state,
265
+ "<div style='padding:24px;color:#888;text-align:center'>All done!</div>",
266
+ "**All done.**",
267
+ "",
268
+ status,
269
+ _global_stats_md(),
270
+ )
271
  video_html, meta, prompt = _load_item(order, 0)
272
+ skipped = len(POOL) - len(order)
273
+ status = (
274
+ f"✅ Logged in as `{annotator}` — {len(order)} items to annotate"
275
+ + (f" (skipped {skipped} already done)." if skipped else ".")
276
+ )
277
+ return state, video_html, meta, prompt, status, _global_stats_md()
278
 
279
 
280
  def submit_and_next(state: dict, score: float, note: str):
281
  if not state or "order" not in state:
282
+ return (
283
+ state, "", "⚠️ Please log in first.", "", 3, "",
284
+ "⚠️ Not logged in.", _global_stats_md(),
285
+ )
286
  order = state["order"]
287
  idx = state["idx"]
288
  if idx >= len(order):
289
+ return (
290
+ state, "", "**All done!**", "", 3, "",
291
+ "No more items.", _global_stats_md(),
292
+ )
293
  model, task_id = POOL[order[idx]]
294
  record = {
295
  "timestamp": time.time(),
 
304
  _append_annotation(record)
305
  state["idx"] = idx + 1
306
  video_html, meta, prompt = _load_item(state["order"], state["idx"])
307
+ return (
308
+ state, video_html, meta, prompt, 3, "",
309
+ f"✅ Submitted ({state['idx']}). Next →", _global_stats_md(),
310
+ )
311
 
312
 
313
  def skip_and_next(state: dict):
314
  if not state or "order" not in state:
315
+ return (
316
+ state, "", "⚠️ Please log in first.", "", 3, "",
317
+ "⚠️ Not logged in.", _global_stats_md(),
318
+ )
319
  state["idx"] = state["idx"] + 1
320
  video_html, meta, prompt = _load_item(state["order"], state["idx"])
321
+ return (
322
+ state, video_html, meta, prompt, 3, "",
323
+ f"⏭️ Skipped. Position: {state['idx']}", _global_stats_md(),
324
+ )
325
 
326
 
327
  # ---------------------------------------------------------------------------
 
337
  2. Click **Start** — a video will appear below.
338
  3. Give a score (1–5, 5 = best) and optional note; click **Submit & Next**.
339
  4. Submissions auto-sync to the dataset repo every 5 minutes.
340
+
341
+ _Tip_: items you've already annotated are automatically skipped.
342
  """
343
  )
344
 
345
+ stats_md = gr.Markdown(_global_stats_md())
346
+
347
  state = gr.State()
348
 
349
  with gr.Row():
 
381
  login_btn.click(
382
  start_session,
383
  inputs=[annotator_in, state],
384
+ outputs=[state, video, meta_md, prompt_tb, status_md, stats_md],
385
  api_name=False,
386
  )
387
  annotator_in.submit(
388
  start_session,
389
  inputs=[annotator_in, state],
390
+ outputs=[state, video, meta_md, prompt_tb, status_md, stats_md],
391
  api_name=False,
392
  )
393
  submit_btn.click(
394
  submit_and_next,
395
  inputs=[state, score, note],
396
+ outputs=[state, video, meta_md, prompt_tb, score, note, status_md, stats_md],
397
  api_name=False,
398
  )
399
  skip_btn.click(
400
  skip_and_next,
401
  inputs=[state],
402
+ outputs=[state, video, meta_md, prompt_tb, score, note, status_md, stats_md],
403
  api_name=False,
404
  )
405