Skip to content

Add NetTCR-2.2 pMHC:TCR binding predictor (#215) - #216

Merged
iskandr merged 2 commits into
masterfrom
nettcr-predictor
Jul 8, 2026
Merged

Add NetTCR-2.2 pMHC:TCR binding predictor (#215)#216
iskandr merged 2 commits into
masterfrom
nettcr-predictor

Conversation

@iskandr

@iskandr iskandr commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Closes #215.

Adds TCR specificity as a new modality to mhctools — predicting whether a paired αβ T-cell receptor recognises a class-I peptide. This is the first predictor whose input is a receptor (six CDR loops) rather than an MHC allele, and the first pMHC:TCR output.

What's new

Ontology / data model

  • Kind.pMHC_TCR_binding — new output kind.
  • TCR — new input type: a paired αβ receptor given by its six CDR loops (cdr1acdr3b) + optional name. NetTCR-style a1b3 aliases and a stable .identifier.
  • Prediction.tcr field + tcr dataframe column (empty for non-TCR predictions, same convention as allele on processing predictors).
  • PeptideResult.tcr_binding and .tcrs accessors.

Predictor

  • NetTCR — runs NetTCR-2.2's pan cross-validation ensemble (20 TFLite models, ~0.4 MB each) in-process; the reported score is the ensemble mean recognition probability.
from mhctools import NetTCR, TCR

p = NetTCR()   # resolves NETTCR_DIR / ~/NetTCR-2.2
tcr = TCR(cdr1a="NSASQS", cdr2a="VYSSG", cdr3a="VVEGDKVI",
          cdr1b="MGHRA", cdr2b="YSYEKL", cdr3b="ASSHSGYEQF", name="clone1")

p.predict_pairs([("LLWNGPMAV", tcr)])[0].tcr_binding.score   # -> ~0.77
p.predict(["LLWNGPMAV", "GILGFVFTL"], [tcr])                 # peptide × TCR grid

Distribution — no gated download, no conda env

NetTCR ships its weights directly in its git repo (not git-LFS), so the wrapper loads the TFLite models directly and does not need NetTCR's conda/Keras environment. Requirements:

  • a cloned NetTCR-2.2 (weights) — resolved from nettcr_path=, NETTCR_DIR, or ~/NetTCR-2.2 / ~/code/NetTCR-2.2;
  • a TFLite runtime — pip install mhctools[nettcr] (uses the lightweight ai-edge-litert; tflite-runtime and tensorflow also work via a runtime fallback).

Nothing from NetTCR (which is under an academic software license) is vendored — same "user provides the install" model as the DTU netMHC* tools and BigMHC.

Faithfulness — verified against upstream

The BLOSUM50 encoding (residues → BLOSUM50/5, padding → −1), per-feature padding lengths (pep=12, a1..b3=7/8/22/6/7/23), and name-based input-tensor assignment (serving_default_<feature>:0) reproduce NetTCR-2.2 src/predict.py exactly. Ran NetTCR's own predict.py for a model and matched this wrapper's per-model output to ~1e-7; the ensemble recovers sensible binding signal (cognate LLWNGPMAV pair 0.77 vs mismatch RAKFKQLL 0.001).

Scope

  • In: the pan model (the only one that generalises to arbitrary peptides).
  • Out: the peptide-specific / pretrained per-peptide models, and any NetTCR training mode. Not wired into the CLI (its allele-based interface doesn't fit a TCR input) — Python API only.

Tests

  • tests/test_tcr.pyTCR dataclass (model-free, always runs).
  • tests/test_nettcr.py — encoding unit tests that pin the exact BLOSUM50/pad/normalize behaviour (model-free, always run) + integration tests gated on a NetTCR install (skip when absent, mirroring test_bigmhc.py).

Full suite: 462 passed, 9 skipped, 2 xfailed (with NETTCR_DIR set so the model tests execute). Ruff clean. Version bump 3.14.1 → 3.15.0.

https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG

iskandr added 2 commits July 8, 2026 16:02
Adds TCR specificity as a new modality: predicts whether a paired αβ TCR
recognises a class-I peptide. This is the first predictor whose input is a
receptor rather than an MHC allele, and the first pMHC:TCR output.

New public API:
- `Kind.pMHC_TCR_binding` output kind
- `TCR` input type (six CDR loops + optional name)
- `NetTCR` predictor: runs NetTCR-2.2's pan CV ensemble (20 TFLite models)
  in-process, returning the ensemble-mean recognition probability
- `Prediction.tcr` field + `tcr` dataframe column; `PeptideResult.tcr_binding`
  and `.tcrs` accessors

NetTCR ships its weights in-repo as small TFLite models, so the wrapper
loads them directly (via ai-edge-litert / tflite-runtime / tensorflow) and
does not need NetTCR's conda env — only a cloned repo (NETTCR_DIR) and a
TFLite runtime (`pip install mhctools[nettcr]`). Nothing is vendored.

The BLOSUM50 encoding, per-feature padding lengths, and name-based input
tensor assignment reproduce NetTCR-2.2 `src/predict.py` exactly — verified
to ~1e-7 against its own output on held-out pairs.

Version bump 3.14.1 -> 3.15.0.

Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
Fixes from PR #216 self-review:
- _find_nettcr_dir now validates an explicit nettcr_path / NETTCR_DIR up
  front, failing with a clear message instead of a late "no models found"
- Clear ValueError (not a bare KeyError) if a model exposes an unexpected
  input tensor whose parsed feature name isn't one of the 7 expected
- Cache tensor allocation by batch size, so repeated same-size predict()
  calls skip re-resize/re-allocate
- Move the n==0 early return ahead of model loading; import pandas at module
  top; document that a NetTCR instance is not thread-safe

Tests:
- Scope the skip marker to model-dependent tests only, so the encoding and
  constructor-error tests now run in CI without a NetTCR install (previously
  a module-level pytestmark skipped the whole file)
- Add model-free constructor error-path tests (missing path, no models)
- Add test_reproduces_published_ensemble: asserts the wrapper matches
  NetTCR-2.2's own 20-pan-model ensemble (generated by running upstream
  src/predict.py over all 20 checkpoints and averaging) to <1e-3 on six real
  TCRs from NetTCR's own dataset; plus a labeled-binder-ranks-top check and a
  batch-vs-single consistency check for the allocation-caching path

Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
@iskandr
iskandr merged commit e795971 into master Jul 8, 2026
4 checks passed
@iskandr
iskandr deleted the nettcr-predictor branch July 8, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Is a NetTCR-2.2 (pMHC:TCR binding) predictor in scope for mhctools?

1 participant