Design the codebook
Step 3 of the Getting started workflow.
The codebook assigns each target three readout bits — the combination that identifies it during imaging. It is the contract between your biological targets and the encoding, and everything downstream is built against it. A codebook that is wrong produces probes that are individually perfect and collectively undecodable, so it is worth getting right before the expensive step.
If the idea of combinatorial bits is new, What SOLAR is and what these probes do explains what the bits physically are and why panels are encoded this way.
Generate it
mkprobes make-codebook data/mouse panel_a/genes.converted.tss.txt -o panel_a/codebook.json
Pass -o explicitly. Without it, the output name is derived from the
input list rather than being a fixed default: genes.converted.tss.txt
produces genes.converted.tss.codebook.json. That is rarely what anyone
expects, and the manifest you generated with mkprobes init refers to
codebook.json.
What the command does:
Picks the smallest vendored MHD (minimum-Hamming-distance) code whose capacity exceeds your target count by at least 5%.
Assigns each target three readout bits.
Fills the spare capacity with
Blank-Ndecoy codewords — these are how you measure the false-positive rate on real data.Swaps any codeword that would perfectly confound imaging rounds onto a blank.
Logs a hash of the finished codebook.
--n-bits overrides the automatic sizing; you rarely want to.
Optional: balance the readout load
If you have per-target expression data, the assignment can be optimized so no single readout bit is dominated by a handful of very highly expressed genes:
# a table registered on the dataset...
mkprobes make-codebook data/mouse panel_a/genes.converted.tss.txt -o panel_a/codebook.json \
--expression fpkm
# ...or any file on disk
mkprobes make-codebook data/mouse panel_a/genes.converted.tss.txt -o panel_a/codebook.json \
--expression expression.tsv --expression-column tpm
This is genuinely optional — omit it and you get a plain seeded assignment
(--seed), which is a perfectly good panel.
The table needs a transcript_id and/or gene_id column. Targets missing
from it are filled with the table median, with a warning. --iterations
(default 200) sets how many assignments are tried; the one with the most even
per-bit load wins. --expression-column is only needed when the value column
is ambiguous.
For single-cell-derived optimization, the Python API offers
CodebookPickerSingleCell.find_optimalish, which balances per-cell load by
percentile.
Extending an existing panel
To add targets to a panel you have already ordered, without reusing bits:
mkprobes make-codebook data/mouse new_genes.tss.txt -o panel_b/codebook.json \
--existing-codebook panel_a/codebook.json
This derives the bit offset from the old codebook and refuses gene or bit
overlap rather than silently colliding. --offset sets the offset by hand and
is mutually exclusive with --existing-codebook.
Which codebook produced a given file
Every codebook has a short hash — a stable identifier for that exact set of targets and bit assignments. You do not have to record it anywhere: the tool does.
make-codebook writes it beside the codebook, so it outlives the terminal:
panel_a/codebook.json
panel_a/codebook.hash <- 25dd20
It is also stamped into the provenance of every file designed against that
codebook — each per-target _final_*.parquet, and the assembled pool. So the
question "which codebook produced this?" is answered by the file itself:
mkprobes provenance panel_a/output/Sox2-201_final_BamHIKpnI_2,10,18.parquet
{
"codebook_hash": "25dd20",
"bits": [2, 10, 18],
"stage": "construct",
...
}
Match that against codebook.hash to confirm an output came from the codebook
you think it did. This matters when a codebook is regenerated: a different seed
or an edited target list produces different bit assignments, and the outputs
are otherwise indistinguishable — same target, same file name, different panel.
To print the hash of any codebook directly:
mkprobes hash panel_a/codebook.json
Note
The hash covers the codebook as written, Blank codes included. Re-serialising it — different indentation, different key order — does not change it, because hashing sorts the keys first. Changing any target's bits does.
What a codebook looks like
{
"Sox2-201": [2, 10, 18],
"Pax6-201": [1, 9, 17],
"Blank-1": [3, 11, 19]
}
The rules mkprobes enforces, and which you should preserve if you ever edit
one by hand:
A JSON object at the top level.
Keys are target names matching your target list exactly.
Values are arrays of exactly three integers.
The three integers within a target are distinct.
No two targets share the same three-bit code.
Generally: do not edit it by hand. Regenerate it. The file is cheap to produce and hand edits are how rules 4 and 5 get broken.
Before you spend hours on probes
Check that the codebook and the target list still agree — a target list edited after the codebook was generated is a common and expensive mistake:
python - <<'EOF'
import json
from pathlib import Path
targets = {
line.split("#", 1)[0].strip()
for line in Path("panel_a/genes.converted.tss.txt").read_text().splitlines()
if line.split("#", 1)[0].strip()
}
codebook = {k for k in json.loads(Path("panel_a/codebook.json").read_text()) if not k.startswith("Blank")}
if missing := sorted(targets - codebook):
raise SystemExit(f"Missing from codebook: {missing}")
if extra := sorted(codebook - targets):
raise SystemExit(f"In codebook but not in target list: {extra}")
print(f"OK - {len(targets)} targets aligned")
EOF
If that fails, regenerate the codebook from the current target list rather than patching either file.
When it goes wrong
Codebook names do not match the target list — regenerate from
genes.converted.tss.txt, do not rename keys.You changed the target list after generating the codebook — regenerate and re-hash. Any probes already designed against the old codebook are still valid for the targets that kept their bits, but
run-panelworks from the codebook, so the two must agree.Two panels imaged together reuse bits — generate the second with
--existing-codebookpointing at the first.
Next: Design probes.