Skip to content

PIASOmarkerDB API client

PIASOmarkerDB is a curated collection of cell-type marker genes with specificity scores. piaso.tl.getMarkers is the Python client — no key, no separate install.

import piaso

1. What is in there

studies = piaso.tl.getMarkers(list_studies=True)
len(studies), studies[:6]
(36,
['AllenAgeingBrainAtlas2025_supertype',
'AllenDevVisualCortex2025_RNA',
'AllenHumanImmuneHealthAtlas_L2',
'AllenHumanImmuneHealthAtlas_L3',
'AllenWholeMouseBrain_GABA',
'AllenWholeMouseBrain_Neuron'])

Thirty-six studies spanning human and mouse, brain, blood, bone marrow, breast, thymus and whole-body atlases.

2. One study

df = piaso.tl.getMarkers(study="AllenWholeMouseBrain_isocortex")
df.shape, list(df.columns)
((1300, 7),
['cell_type', 'condition', 'gene', 'species', 'specificity_score',
'study_publication', 'tissue'])
cell_type condition gene species specificity_score
056 Sst Chodl Gaba normal Chodl Mouse 14.086876
056 Sst Chodl Gaba normal Krt18 Mouse 13.081692
051 Pvalb chandelier Gaba normal Vmn1r209 Mouse 12.826864
330 VLMC NN normal Slc22a6 Mouse 11.932269

specificity_score is a COSG score: higher means the gene is more exclusive to that type. Chodl topping Sst Chodl Gaba is the sanity check — the type is named after the gene.

For anything that consumes marker sets, ask for the dictionary as well. It returns both, so unpack two names:

markers_df, marker_sets = piaso.tl.getMarkers(
study="AllenWholeMouseBrain_isocortex", as_dict=True)
len(marker_sets)
26

Assigning that to one variable hands the tuple to whatever you call next — a mistake that reached three published tutorials before it was caught.

3. Ask the other way round

The client is not only “give me markers for this study”. Every field filters.

By gene — where is this gene a marker, across all studies?

piaso.tl.getMarkers(gene="Sst")
114 rows
cell_type gene species specificity_score study_publication
enteroendocrine cell SST Human 12.843140 TabulaSapiens_10X
enteroendocrine cell of small intestine SST Human 7.971432 TabulaSapiens_10X
SST InN SST Human 7.363342 XuTeichmann2023_HippocampalFormation
053 Sst Gaba Sst Mouse 7.326999 AllenWholeMouseBrain_isocortex
Interneurons Sst Mouse 7.298782 DiBellaArlotta2021

Somatostatin is a marker of cortical interneurons and of gut enteroendocrine cells, more specifically of the latter. Useful before assuming a gene means what it means in your tissue.

By cell type, species, score, or a limit:

piaso.tl.getMarkers(cell_type="Pvalb Gaba")
piaso.tl.getMarkers(species="Mouse", limit=5)
piaso.tl.getMarkers(study="AllenWholeMouseBrain_isocortex", min_score=5.0)

Cell type names must match the study’s vocabulary exactly — "Astro-TE" returns 0 rows because the stored name is "319 Astro-TE NN". List them first:

piaso.tl.getMarkers(study="AllenWholeMouseBrain_isocortex",
list_cell_types=True)[:6]
['001 CLA-EPd-CTX Car3 Glut', '004 L6 IT CTX Glut', '005 L5 IT CTX Glut',
'006 L4/5 IT CTX Glut', '007 L2/3 IT CTX Glut', '022 L5 ET CTX Glut']
len(piaso.tl.getMarkers(study="AllenWholeMouseBrain_isocortex",
list_genes=True))
23657

4. From a gene list to a cell type

analyzeMarkers is the reverse query: you have a set of genes — a cluster’s markers, a differential expression hit list — and want to know which annotated cell types they look like.

piaso.tl.analyzeMarkers(["Sst", "Pvalb", "Vip", "Lamp5", "Gad1", "Gad2"])
cell_type study_publication species matched matched_genes avg_specificity
Interneurons DiBellaArlotta2021 Mouse 3 Gad1,Sst,Gad2 7.273702
Lamp5 Gaba_2 AllenAgeingBrainAtlas2025_supertype Mouse 3 Gad1,Lamp5,Gad2 3.712216
Inh_Six3 MacoskoWholeMouseBrain... Mouse 3 Pvalb,Gad2,Gad1 3.241107
742_Pvalb Gaba_3 AllenDevVisualCortex2025_RNA Mouse 3 Gad1,Gad2,Pvalb 3.197737
IN-Mix-LAMP5 WangKriegstein2025 Human 2 LAMP5,GAD2 5.817186
IN-MGE-PV WangKriegstein2025 Human 2 PVALB,GAD2 5.364346

Six canonical GABAergic genes, and every top hit is an inhibitory neuron type — across four independent studies, two species, and both adult and developmental taxonomies. Ranking is by how many of your genes matched, then by average specificity.

This is the fastest sanity check on an unnamed cluster: hand it the top COSG genes and see what the literature already calls it.

5. Annotating clusters, not cells

analyzeMarkers scales up: run COSG on your clusters, hand each cluster’s markers to the database, and take the best match. That labels a whole cluster from its own gene list rather than scoring every cell.

import cosg
piaso.tl.infog(adata, layer="raw", n_top_genes=3000)
piaso.tl.runSVD(adata, layer="infog", n_components=50, key_added="X_svd")
piaso.tl.neighbors(adata, use_rep="X_svd", n_neighbors=15)
piaso.tl.leiden(adata, resolution=1.0, key_added="leiden")
cosg.cosg(adata, key_added="cosg", groupby="leiden", n_genes_user=30, mu=10)
names = pd.DataFrame(adata.uns["cosg"]["names"])
rows = []
for cluster in names.columns:
hits = piaso.tl.analyzeMarkers(list(names[cluster]))
best = hits.iloc[0]
rows.append(dict(leiden=cluster, call=best["cell_type"],
matched=int(best["matched_gene_count"]),
study=best["study_publication"]))
calls = pd.DataFrame(rows)

On the adult mouse cortex reference, 30 Leiden clusters:

leidencallmatched / 30study
3053 Sst Gaba17AllenWholeMouseBrain_isocortex
4OPC NN_121AllenAgeingBrainAtlas2025_supertype
5052 Pvalb Gaba19AllenWholeMouseBrain_isocortex
7032 L5 NP CTX Glut21AllenWholeMouseBrain_isocortex
9Micro_Selplg16MacoskoWholeMouseBrain_nuclei_NonNeuron
20335 BAM NN27AllenWholeMouseBrain_NonNeuron
26333 Endo NN19AllenWholeMouseBrain_isocortex

Two things to notice.

The matched column is the confidence. Cluster 20 matched 27 of its 30 genes to border-associated macrophages — that is not a close call. Clusters matching 3 to 7 genes are guesses, and the column tells you which is which without any extra work.

Different clusters match different studies. The query runs across all 36, so a cluster is labelled by whichever study describes it best rather than by one you picked in advance. The interneurons land on the Allen isocortex taxonomy, the glia on Macosko and the ageing atlas.

adata.obs["cluster_call"] = adata.obs["leiden"].map(
dict(zip(calls.leiden, calls.call)))
piaso.pl.embedding(adata, basis="X_umap", color=["leiden", "cluster_call"])
Clusters and their database calls
piaso.pl.sankey(adata, left="leiden", right="cluster_call")
Leiden clusters to database calls

Checked against this dataset’s own curated annotation, 87.6% of cells carry a reference label whose modal cluster-call is the right correspondence — Oligodendrocyte → MOL and Oligo_Opalin, Microglia → Micro_Selplg, OPC → OPC NN_1, PV → 052 Pvalb Gaba, L6 CT → 030 L6 CT CTX Glut.

Cluster-wise or cell-wise?

cluster-wise (analyzeMarkers)cell-wise (predictCellTypeByMarker)
unitone label per clusterone label per cell
needsa clusteringan embedding
tells youwhich study describes each cluster, and how wellwhich cells disagree with their neighbours
costone API call per clusterone scoring pass

Cluster-wise is the faster first pass and it names the source as well as the type. Cell-wise is what you want when the question is about individual cells — doublets, intermediate states, cells that sit between two labels.

6. Straight into annotation

markers_df, marker_sets = piaso.tl.getMarkers(
study="AllenWholeMouseBrain_isocortex", as_dict=True)
piaso.tl.predictCellTypeByMarker(adata, marker_gene_set=marker_sets,
score_layer="infog", use_rep="X_svd",
key_added="CellTypes")

Worked through, with accuracy numbers, in marker-based cell type prediction.

Notes

  • Results are cached locally after the first call, so repeated queries in a session do not re-hit the API.
  • piaso.tl.queryPIASOmarkerDB is the full-featured entry point; getMarkers is the short alias, and takes the same arguments.
  • Intersect marker sets with your own var_names and drop the types left with too few genes — a type scored on two surviving genes is scored on almost no evidence.