vdjmatch#

Fast, control-calibrated annotation of T-cell receptor antigen specificity.

vdjmatch annotates clonotypes in large AIRR repertoires against VDJdb by fuzzy CDR3 search, reporting a control-calibrated E-value (BLAST-style significance against a background repertoire) and enriched antigen-specificity labels. It is a Python rewrite of the legacy Java/Groovy vdjmatch, built on the seqtree search core.

Note

vdjmatch 0.2.0 is an early release under active development. The single-chain and paired α/β annotators — VDJdb fetch, AIRR I/O, fuzzy search, control-calibrated single- and paired-chain E-values, epitope-enrichment summaries, and the CLI — are in place; the re-derived segment-aware substitution matrix (VDJAM) and the full tool-comparison benchmark are still settling.

Installation#

pip install vdjmatch

seqtree (the search engine) is installed as a dependency. For development:

python -m venv .venv && source .venv/bin/activate
pip install -e ".[test,bench]"

Quickstart#

Fetch the latest VDJdb release and annotate an AIRR rearrangement sample:

vdjmatch update                                   # cache the latest VDJdb release
vdjmatch match --species HomoSapiens --scope 1,0,0,1 -o out sample_airr.tsv

This writes three tab-separated tables per sample:

  • out.<sample>.hits.txt — every query→VDJdb hit with CDR3 alignment, CIGAR, edit counts and score.

  • out.<sample>.calls.txt — one predicted epitope per query clonotype with its E-value.

  • out.<sample>.summary.txt — epitope-level enrichment (unique clonotypes, reads) by MHC class and antigen species.

Key ideas#

Control-calibrated E-value. Immune repertoires are biologically redundant (convergent recombination, public clones), so a naive i.i.d. null massively over-calls. vdjmatch counts a query’s VDJdb neighbours within a fixed search scope and compares to the count expected from a matched background control repertoire; the Poisson-tail p_enrichment is significant only when a clonotype has more VDJdb neighbours than the generative process predicts — the hallmark of antigen-driven selection. The theory is derived in the seqtree appendix.

First-hit (adaptive) scope. Rather than fix the ball, vdjmatch widens to each query’s nearest VDJdb hit (up to 5 edits, ≤2 ins, ≤2 del) and evaluates the E-value at that radius: the background count grows with the radius, so a distance-1 hit is significant while a distance-5-only hit is not — random repertoires are filtered without a hand-tuned scope (vdjmatch.evalue.first_hit).

Scope / budget. --scope s,i,d,t sets the maximum substitutions, insertions, deletions and total edits of the CDR3 search ball.

VDJAM. A TCR-specific amino-acid substitution matrix (bundled), with optional region-aware weighting that emphasises the antigen-contacting NDN core over the germline-fixed V/J flanks (germline-retention profiles derived from the OLGA model via mirpy).

What scoring actually buys. An empirical study on VDJdb (the scoring appendix; 2026-06-11-ZENODO release, composition-controlled) finds that Hamming distance 1 is the signal:noise optimum (macro purity 0.49 → 0.07 over edit distance 1–5; the original VDJdb observation), that central substitutions carry the specificity signal (P(same) ≈ 0.31 in the core vs ≈ 0.75 near the anchors), and that no amino-acid matrix clearly beats BLOSUM62 — a genetic-code null (VDJAMr) even ties it, so CDR3 substitution structure is generative, not chemical. Reweighting BLOSUM62 by the central-position significance (a native seqtree PositionalMatrix, end-anchored) is the one change that does beat it, in 7/8 held-out epitopes. The substitution alphabet is second-order, position is the first-order matrix lever, and the overall first-order statistic is the control-calibrated E-value. Finally, the V gene is a strong near-binary prior (same-V neighbours share an epitope up to ~7× more than cross-V); loose CDR1/CDR2 similarity does not recover it, and near-exact germline-loop identity recovers only about half of it (the rest is gene-identity-specific).