You inherit a cross-dataset cell-type annotation problem. You are given a labelled reference set
(single cells with cell-type labels) and an unlabelled query set whose cells were assayed in a
different experiment — different lab, different protocol, different batch. Using a shipped
single-cell foundation model at inference time only, you must assign a cell type to every query
cell. Your submitted annotate() is re-run unchanged on a sealed query drawn from an experiment you
never see, and scored by macro-F1 against hidden ground-truth labels.
The difficulty is the cross-experiment distribution shift: a label-transfer pipeline that looks healthy when reference and query come from the same protocol degrades sharply once the query comes from an unseen one. Closing that gap without touching the model's weights is the research problem.
Hard Constraints
- Single GPU, 24 h wall clock.
- No fine-tuning and no re-training of the foundation model — forward passes to extract embeddings only. Do not train a new learned model on the query.
- Co-annotation regime: you may use the reference labels and the query expression matrix only. You must not read, reconstruct, or infer the query ground-truth labels from any source.
- Do not submit precomputed labels.
annotate()must compute its output from the arrays it is handed, at grading time, on data it has not seen. - Only files under
environment/methods/main/are graded; do not modifyselfcheck.pyordata/.
What You Have
data/visible/— a reference set plus a query from a different dataset, with labels used only byselfcheck.pyfor local scoring.methods/main/solver.py— the starting method: default embeddings plus a plain nearest-neighbour label transfer. It is deliberately weak and it is what you replace.- The foundation-model weights, provided by the image; the path arrives in
item["scgpt_model_dir"]. python environment/selfcheck.py— free and unlimited: runs your currentannotate()on the visible query and prints macro-F1.
What You Submit
Edit environment/methods/main/solver.py, keeping this exact signature:
annotate(item: dict) -> dict
# item: {
# "reference_h5ad": <labelled reference, .X = raw counts, .obs["cell_type"] = labels>,
# "query_h5ad": <unlabelled query, .X = raw counts, no labels>,
# "scgpt_model_dir": <foundation-model weight directory>,
# }
# returns: {"labels": [<predicted cell_type string per query cell>, ...]} # query .obs order
You may add sibling modules under main/ and import them. There is no submit step and no
per-attempt feedback: whatever sits in methods/main/ at the end is what gets re-run and graded.
How It Is Judged
A sealed verifier copies your methods/main/ into a clean sandbox with no network and no access to
labels, calls annotate() once on the held-out query, and computes macro-F1 (over the cell types
present in the ground truth) between your returned labels and the sealed truth — higher is better.
The verifier recomputes the metric itself from your labels; it does not read any score you report.
You receive no feedback from this run.