Choose your targets
Step 2 of the Getting started workflow.
Two commands turn the list of genes you care about into the transcript-level target list everything downstream uses. Doing this before the codebook exists is deliberate: a name that turns out to be wrong is free to fix now and expensive to fix after probes have been designed against it.
Writing the target list
One target per line. Blank lines are ignored, everything after a # is a
comment, and inline comments work, so the file can carry its own rationale:
# Panel A - dorsal telencephalon, v2
Sox2
Pax6 # dorsal telencephalon marker
Eomes
# Gad1 - dropped, too low in this region
For a reference dataset, use gene names. For a custom dataset, use whatever IDs its annotation actually carries — gene IDs, transcript IDs, or symbols resolvable through a registered annotation table.
Writing targets as gene names on a custom dataset
De novo annotations usually have no gene names at all: mkprobes ingest
reports GENE_NAME_FALLBACK and names fall back to IDs like Och.958.1,
which nobody wants to write a panel in. If you have a table mapping those IDs
to names — an ortholog assignment, a curated symbol list — register it and say
which column holds the names you want to use:
mkprobes ingest data/myspecies --genome genome.fa --gtf annotation.gtf \
--species myspecies \
--annotation-table annot=my_annotation.tsv \
--gene-name-column Hsapiens_gene_name
Targets can then be written as those names, and chkgenes resolves them to
transcript IDs. Two details worth knowing:
Only that column is searched. Without
--gene-name-column, lookup scans every text column of every registered table. That is fine on a small table and slow on a wide one — and it will happily match a protein sequence or an embedding column if something in it looks like your gene name.Comma-separated cells count as one name each. Ortholog tables routinely map one transcript to several symbols (
UBE2A,UBE2B), so each entry is matched separately rather than requiring the whole cell to match.
The column must exist in one of the registered tables; ingest checks at build time and lists the available columns if it does not.
Naming a target twice is a hard error, not a warning: the duplicate would claim a second set of readout bits and silently corrupt the codebook. Order is preserved, because it feeds bit assignment.
Optional: let expression data fill out the panel
Skip this section if you already know every gene you want.
A panel has room for a fixed number of genes, and a hand-picked list tends to
cluster: markers chosen for the same cell type report much the same thing, so
the panel measures one axis of the biology several times over and misses
others. If you have expression data for the tissue — your own, or a published
atlas — suggest-targets proposes genes that carry information your current
choices do not.
mkprobes suggest-targets atlas.h5ad --add 40 --have genes.txt -o genes.txt
It regresses every candidate gene against the ones you already hold, then picks the genes that best span whatever variation is left over. It also reports how much of the data's structure the panel captures, before and after, so the suggestion comes with a number rather than only a list:
Panel of 43 captures 38.5% of the variance in the top 30 PCs
(up from 6.8% with your 3 alone).
The output is an ordinary target list with your genes first, in order, followed by the suggestions. Read it and edit it. These are candidates ranked by a statistical criterion, not by whether they make biological sense, are expressed highly enough to detect, or matter to your question.
Important
Filter your expression data to informative genes first — scanpy's
highly_variable_genes is the usual route.
A gene that correlates with nothing looks maximally independent to this method, so unfiltered data makes it prefer genes that are merely noisy over genes that report real biology. The effect is not subtle: on test data where 8 latent programmes were present, adding a few hundred unstructured genes changed the result from covering most programmes to picking nothing but noise.
The command checks for this and warns when the suggestions look like noise, but the check is a backstop, not a substitute for filtering.
Two options are worth knowing. --layer chooses which expression matrix to use,
if your file has more than one — normalized, log-transformed values usually work
better than raw counts. --n-components sets how many dimensions of the
leftover variation to select against; it defaults to --add + 20 (minimum 50),
and lowering it helps when your data has only a few distinct programmes. The
choice materially changes which genes come out, so it is worth trying a couple
of values and comparing the variance-capture numbers.
1. Check the names
mkprobes chkgenes data/mouse panel_a/genes.txt
This resolves every name against the dataset and writes
panel_a/genes.converted.txt — the same list, normalized. On reference
datasets it checks against Ensembl and writes genes.mapping.json recording
any renames it applied. On custom datasets the check is entirely offline,
against the dataset's own annotation plus any registered alias or ortholog
tables.
Unresolvable names fail loudly, with close-match suggestions. Fix them in
genes.txt and re-run before continuing.
2. Resolve to transcripts
mkprobes convert-to-transcripts data/mouse panel_a/genes.converted.txt
This picks one transcript per gene and writes
panel_a/genes.converted.tss.txt. That file is the target list every later
step takes.
Both commands name their output after their input, so the chain is:
genes.txt
--chkgenes--> genes.converted.txt
--convert-to-transcripts--> genes.converted.tss.txt
If you skip chkgenes and run convert-to-transcripts on genes.txt
directly you get genes.tss.txt instead. Either is fine, as long as you pass
the right filename to the next step.
Picking the selection mode
-m/--mode sets the policy. Fix it before you design probes and do not change
it mid-panel; switching modes changes which sequence probes were designed
against.
Mode |
What it picks |
Where it works |
|---|---|---|
|
the canonical transcript (default) |
reference; falls back to |
|
per gene, the isoform with the longest sequence |
anywhere, offline |
|
every isoform |
anywhere, offline |
|
annotation-specific sets |
reference only (human/mouse) |
On a custom dataset, pass -m longest explicitly. canonical will fall back
to it anyway, and being explicit keeps the command self-documenting.
longest measures the sequence, read from the FASTA, so introns do not
distort the choice.
Looking up one transcript
mkprobes transcripts answers the same question for a single gene without
writing a list — useful for spot-checks:
mkprobes transcripts data/mouse --gene Sox2 --canonical
mkprobes transcripts data/myspecies --gene Och.576 --longest
Here the modes are flags (--longest) rather than -m longest.
When it goes wrong
Transcript not found in ensembl— the name is a transcript where a gene was expected, or the dataset is custom and the reference-only modes do not apply. Use--longest/--all.Could not resolve 'X'— the annotation does not know that symbol. Use the dataset's own IDs, or register an ortholog table when you build the dataset (--annotation-table orthologs=...).lists N target(s) more than once— the target list repeats a name. Every command that reads a target list rejects this before doing any work; remove the repeats and re-run.
Next: Design the codebook.