karimox commited on
Commit
d2a6015
Β·
verified Β·
1 Parent(s): 13d0b34

Redesign: 2 tabs (leaderboard + submit) with HF login

Browse files
Files changed (3) hide show
  1. README.md +8 -1
  2. app.py +122 -33
  3. requirements.txt +1 -1
README.md CHANGED
@@ -8,13 +8,15 @@ sdk_version: 5.50.0
8
  python_version: "3.10"
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
  # PRIMO β€” Patient Representations in Multi-Omics
14
 
15
  **A blind benchmark for transcriptomic foundation models.**
16
 
17
- Embed **every** dataset with your model and upload **one** file. A fixed linear
 
18
  probe grades each hidden task (a dataset may be scored on several targets) over
19
  its frozen folds, and the scores roll up into a per-specialty skill leaderboard.
20
  The datasets are opaque (`d001`, `d002`…) β€” you never see the disease/tissue or
@@ -64,6 +66,8 @@ python evaluator.py --submission my_embeddings.parquet
64
 
65
  ## Space configuration
66
 
 
 
67
  - Set an **`HF_TOKEN`** Space secret (fine-grained) with: **read** on
68
  `ScientaLab/primo` (the public `datasets.yaml` manifest) and
69
  `ScientaLab/primo-labels` (the private `tasks.yaml` registry +
@@ -72,3 +76,6 @@ python evaluator.py --submission my_embeddings.parquet
72
  - Results persist as one normalized `task_results.csv` (`model_name, task_id,
73
  score, submitted_at`) in the results dataset; the leaderboard is recomputed
74
  from it by joining the registry, so it survives Space restarts.
 
 
 
 
8
  python_version: "3.10"
9
  app_file: app.py
10
  pinned: false
11
+ hf_oauth: true
12
  ---
13
 
14
  # PRIMO β€” Patient Representations in Multi-Omics
15
 
16
  **A blind benchmark for transcriptomic foundation models.**
17
 
18
+ The Space has two tabs: a **Leaderboard** and a **Submit** form (sign in with
19
+ Hugging Face). Embed **every** dataset with your model and upload **one** file. A fixed linear
20
  probe grades each hidden task (a dataset may be scored on several targets) over
21
  its frozen folds, and the scores roll up into a per-specialty skill leaderboard.
22
  The datasets are opaque (`d001`, `d002`…) β€” you never see the disease/tissue or
 
66
 
67
  ## Space configuration
68
 
69
+ - **`hf_oauth: true`** (set above) turns on the Submit tab's *Sign in with
70
+ Hugging Face* button; submitting requires a logged-in HF account.
71
  - Set an **`HF_TOKEN`** Space secret (fine-grained) with: **read** on
72
  `ScientaLab/primo` (the public `datasets.yaml` manifest) and
73
  `ScientaLab/primo-labels` (the private `tasks.yaml` registry +
 
76
  - Results persist as one normalized `task_results.csv` (`model_name, task_id,
77
  score, submitted_at`) in the results dataset; the leaderboard is recomputed
78
  from it by joining the registry, so it survives Space restarts.
79
+ - Submitter contact metadata (HF username, email, paper / model links, notes)
80
+ persists to a separate `submissions.csv` in the same **private** results
81
+ dataset β€” it never reaches the public leaderboard.
app.py CHANGED
@@ -34,30 +34,35 @@ from scoring import TaskScore, aggregate, public_facets
34
 
35
  TOKEN = os.environ.get("HF_TOKEN")
36
  RESULTS_FILE = "task_results.csv"
 
37
  RESULT_COLUMNS = ["model_name", "task_id", "score", "submitted_at"]
 
 
 
 
 
 
 
 
 
38
  BASE_COLUMNS = ["model_name", "overall_skill", "submitted_at"]
39
  DETAIL_COLUMNS = ["dataset_id", "status", "skill"]
40
 
41
 
42
- def _read_results() -> pd.DataFrame:
43
  from huggingface_hub import hf_hub_download
44
  from huggingface_hub.utils import EntryNotFoundError, RepositoryNotFoundError
45
 
46
  try:
47
- path = hf_hub_download(
48
- RESULTS_REPO, RESULTS_FILE, repo_type="dataset", token=TOKEN
49
- )
50
  except (RepositoryNotFoundError, EntryNotFoundError):
51
- return pd.DataFrame(columns=RESULT_COLUMNS)
52
  return pd.read_csv(path)
53
 
54
 
55
- def _append_results(rows: list[dict]) -> None:
56
  from huggingface_hub import HfApi
57
 
58
- if not rows:
59
- return
60
- df = pd.concat([_read_results(), pd.DataFrame(rows)], ignore_index=True)
61
  api = HfApi(token=TOKEN)
62
  api.create_repo(RESULTS_REPO, repo_type="dataset", private=True, exist_ok=True)
63
  buffer = io.BytesIO()
@@ -65,12 +70,32 @@ def _append_results(rows: list[dict]) -> None:
65
  buffer.seek(0)
66
  api.upload_file(
67
  path_or_fileobj=buffer,
68
- path_in_repo=RESULTS_FILE,
69
  repo_id=RESULTS_REPO,
70
  repo_type="dataset",
71
  )
72
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  def _round(value: float | None) -> float | None:
75
  return round(value, 4) if value is not None else None
76
 
@@ -211,12 +236,24 @@ def _detail_table(result: dict) -> pd.DataFrame:
211
  return table[DETAIL_COLUMNS]
212
 
213
 
214
- def evaluate(submission_path: str, model_name: str):
 
 
 
 
 
 
 
 
215
  empty = pd.DataFrame(columns=DETAIL_COLUMNS)
 
 
216
  if not submission_path:
217
  return "Please upload a submission file.", leaderboard(), empty
218
  if not model_name or not model_name.strip():
219
  return "Please enter a model name.", leaderboard(), empty
 
 
220
  try:
221
  result = score_all(submission_path, TOKEN)
222
  except SubmissionError as error:
@@ -251,8 +288,18 @@ def evaluate(submission_path: str, model_name: str):
251
  }
252
  for task in result["per_task"]
253
  ]
 
 
 
 
 
 
 
 
 
254
  try:
255
  _append_results(rows)
 
256
  except Exception as error: # noqa: BLE001
257
  summary += f"\n\n⚠️ scored, but leaderboard not saved: {error}"
258
  return summary, leaderboard(), detail
@@ -261,32 +308,74 @@ def evaluate(submission_path: str, model_name: str):
261
  def build_demo() -> gr.Blocks:
262
  with gr.Blocks(title="PRIMO Benchmark") as demo:
263
  gr.Markdown(
264
- "# 🧬 PRIMO\n\n"
265
  "A **blind benchmark for transcriptomic foundation models** β€” grade "
266
  "your model's patient-level embeddings against real clinical signal, "
267
- "without ever seeing the labels.\n\n"
268
- "**Submit in 3 steps:**\n"
269
- "1. **Get the data** β†’ download the opaque datasets from "
270
- "[ScientaLab/primo](https://huggingface.co/datasets/ScientaLab/primo)"
271
- " (start with its `datasets.yaml`).\n"
272
- "2. **Embed every dataset** β†’ build **one** file: `dataset_id`, "
273
- "`sample_id`, then one column per embedding dim (`e0`, `e1`, …). "
274
- "CSV / TSV / Parquet, or NPZ.\n"
275
- "3. **Upload below**, name your model, and hit **Evaluate**.\n\n"
276
- "A fixed linear probe scores each hidden task (AUROC or Pearson), "
277
- "rescaled to a 0–1 skill and rolled up per specialty. **Only "
278
- "submissions covering every dataset are ranked.**"
279
  )
280
- model_tb = gr.Textbox(label="Model name", placeholder="e.g. eva-rna-v1")
281
- file_in = gr.File(
282
- label="Submission (.csv / .tsv / .parquet / .npz)", type="filepath"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  )
284
- run_btn = gr.Button("Evaluate", variant="primary")
285
- result_md = gr.Markdown()
286
- board = gr.Dataframe(label="Leaderboard", interactive=False)
287
- detail = gr.Dataframe(label="Your submission β€” per dataset", interactive=False)
288
-
289
- run_btn.click(evaluate, [file_in, model_tb], [result_md, board, detail])
290
  demo.load(leaderboard, None, board)
291
  return demo
292
 
 
34
 
35
  TOKEN = os.environ.get("HF_TOKEN")
36
  RESULTS_FILE = "task_results.csv"
37
+ SUBMISSIONS_FILE = "submissions.csv"
38
  RESULT_COLUMNS = ["model_name", "task_id", "score", "submitted_at"]
39
+ SUBMISSION_COLUMNS = [
40
+ "model_name",
41
+ "submitted_at",
42
+ "hf_username",
43
+ "email",
44
+ "paper_link",
45
+ "hf_model_link",
46
+ "notes",
47
+ ]
48
  BASE_COLUMNS = ["model_name", "overall_skill", "submitted_at"]
49
  DETAIL_COLUMNS = ["dataset_id", "status", "skill"]
50
 
51
 
52
+ def _read_csv(filename: str, columns: list[str]) -> pd.DataFrame:
53
  from huggingface_hub import hf_hub_download
54
  from huggingface_hub.utils import EntryNotFoundError, RepositoryNotFoundError
55
 
56
  try:
57
+ path = hf_hub_download(RESULTS_REPO, filename, repo_type="dataset", token=TOKEN)
 
 
58
  except (RepositoryNotFoundError, EntryNotFoundError):
59
+ return pd.DataFrame(columns=columns)
60
  return pd.read_csv(path)
61
 
62
 
63
+ def _upload_csv(filename: str, df: pd.DataFrame) -> None:
64
  from huggingface_hub import HfApi
65
 
 
 
 
66
  api = HfApi(token=TOKEN)
67
  api.create_repo(RESULTS_REPO, repo_type="dataset", private=True, exist_ok=True)
68
  buffer = io.BytesIO()
 
70
  buffer.seek(0)
71
  api.upload_file(
72
  path_or_fileobj=buffer,
73
+ path_in_repo=filename,
74
  repo_id=RESULTS_REPO,
75
  repo_type="dataset",
76
  )
77
 
78
 
79
+ def _read_results() -> pd.DataFrame:
80
+ return _read_csv(RESULTS_FILE, RESULT_COLUMNS)
81
+
82
+
83
+ def _append_results(rows: list[dict]) -> None:
84
+ if not rows:
85
+ return
86
+ df = pd.concat([_read_results(), pd.DataFrame(rows)], ignore_index=True)
87
+ _upload_csv(RESULTS_FILE, df)
88
+
89
+
90
+ def _append_submission(meta: dict) -> None:
91
+ """Persist a submitter's contact metadata to the private results repo."""
92
+ df = pd.concat(
93
+ [_read_csv(SUBMISSIONS_FILE, SUBMISSION_COLUMNS), pd.DataFrame([meta])],
94
+ ignore_index=True,
95
+ )
96
+ _upload_csv(SUBMISSIONS_FILE, df)
97
+
98
+
99
  def _round(value: float | None) -> float | None:
100
  return round(value, 4) if value is not None else None
101
 
 
236
  return table[DETAIL_COLUMNS]
237
 
238
 
239
+ def evaluate(
240
+ submission_path: str,
241
+ model_name: str,
242
+ email: str,
243
+ paper_link: str,
244
+ hf_model_link: str,
245
+ notes: str,
246
+ profile: gr.OAuthProfile | None,
247
+ ):
248
  empty = pd.DataFrame(columns=DETAIL_COLUMNS)
249
+ if profile is None:
250
+ return "Please sign in with Hugging Face to submit.", leaderboard(), empty
251
  if not submission_path:
252
  return "Please upload a submission file.", leaderboard(), empty
253
  if not model_name or not model_name.strip():
254
  return "Please enter a model name.", leaderboard(), empty
255
+ if not email or not email.strip():
256
+ return "Please enter a contact email.", leaderboard(), empty
257
  try:
258
  result = score_all(submission_path, TOKEN)
259
  except SubmissionError as error:
 
288
  }
289
  for task in result["per_task"]
290
  ]
291
+ meta = {
292
+ "model_name": model,
293
+ "submitted_at": submitted_at,
294
+ "hf_username": profile.username,
295
+ "email": email.strip(),
296
+ "paper_link": (paper_link or "").strip(),
297
+ "hf_model_link": (hf_model_link or "").strip(),
298
+ "notes": (notes or "").strip(),
299
+ }
300
  try:
301
  _append_results(rows)
302
+ _append_submission(meta)
303
  except Exception as error: # noqa: BLE001
304
  summary += f"\n\n⚠️ scored, but leaderboard not saved: {error}"
305
  return summary, leaderboard(), detail
 
308
  def build_demo() -> gr.Blocks:
309
  with gr.Blocks(title="PRIMO Benchmark") as demo:
310
  gr.Markdown(
311
+ "# 🧬 PRIMO β€” Patient Representations in Multi-Omics\n\n"
312
  "A **blind benchmark for transcriptomic foundation models** β€” grade "
313
  "your model's patient-level embeddings against real clinical signal, "
314
+ "without ever seeing the labels."
 
 
 
 
 
 
 
 
 
 
 
315
  )
316
+ with gr.Tabs():
317
+ with gr.Tab("πŸ† Leaderboard"):
318
+ gr.Markdown(
319
+ "Overall skill plus a skill per medical specialty, on a 0–1 "
320
+ "scale (0 = chance, 1 = perfect). **Only submissions covering "
321
+ "every dataset are ranked.**"
322
+ )
323
+ board = gr.Dataframe(label="Leaderboard", interactive=False)
324
+ with gr.Tab("πŸ“€ Submit"):
325
+ gr.Markdown(
326
+ "# Model submission\n\n"
327
+ "1. **Get the data** β†’ download the opaque datasets from "
328
+ "[ScientaLab/primo](https://huggingface.co/"
329
+ "datasets/ScientaLab/primo) (start with its `datasets.yaml`).\n"
330
+ "2. **Embed every dataset** β†’ build **one** file: `dataset_id`, "
331
+ "`sample_id`, then one column per embedding dim (`e0`, `e1`, …). "
332
+ "CSV / TSV / Parquet, or NPZ.\n"
333
+ "3. **Sign in, fill the form, and hit Evaluate.** A fixed linear "
334
+ "probe scores each hidden task (AUROC or Pearson), rescaled to "
335
+ "a 0–1 skill and rolled up per specialty."
336
+ )
337
+ gr.LoginButton()
338
+ with gr.Row():
339
+ with gr.Column():
340
+ model_tb = gr.Textbox(
341
+ label="Model name",
342
+ placeholder="e.g. eva-rna-v1",
343
+ info="Shown on the leaderboard.",
344
+ )
345
+ email_tb = gr.Textbox(
346
+ label="Email address",
347
+ placeholder="you@lab.org",
348
+ info="Contact for this submission β€” kept private.",
349
+ )
350
+ notes_tb = gr.Textbox(
351
+ label="Training data / notes (optional)",
352
+ placeholder="e.g. pretrained on atlas X",
353
+ info="About the model or its training data.",
354
+ )
355
+ with gr.Column():
356
+ paper_tb = gr.Textbox(
357
+ label="Paper link (optional)",
358
+ placeholder="https://arxiv.org/abs/...",
359
+ )
360
+ hf_tb = gr.Textbox(
361
+ label="Hugging Face model link (optional)",
362
+ placeholder="https://huggingface.co/...",
363
+ )
364
+ file_in = gr.File(
365
+ label="Submission (.csv / .tsv / .parquet / .npz)",
366
+ type="filepath",
367
+ )
368
+ run_btn = gr.Button("Evaluate", variant="primary")
369
+ result_md = gr.Markdown()
370
+ detail = gr.Dataframe(
371
+ label="Your submission β€” per dataset", interactive=False
372
+ )
373
+
374
+ run_btn.click(
375
+ evaluate,
376
+ [file_in, model_tb, email_tb, paper_tb, hf_tb, notes_tb],
377
+ [result_md, board, detail],
378
  )
 
 
 
 
 
 
379
  demo.load(leaderboard, None, board)
380
  return demo
381
 
requirements.txt CHANGED
@@ -3,5 +3,5 @@ pandas
3
  scikit-learn
4
  pyyaml
5
  huggingface_hub<1.0
6
- gradio==5.50.0
7
  audioop-lts; python_version >= "3.13"
 
3
  scikit-learn
4
  pyyaml
5
  huggingface_hub<1.0
6
+ gradio[oauth]==5.50.0
7
  audioop-lts; python_version >= "3.13"