Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 197 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,188 @@
<img src="https://img.shields.io/pypi/v/mhctools.svg?maxAge=1000" alt="PyPI" />
</a>

<!--
<a href='https://mhctools.readthedocs.io/en/latest/?badge=latest'>
<img src='https://readthedocs.org/projects/mhctools/badge/?version=latest' alt='Documentation Status' />
</a>
-->
<!--
[![DOI](https://zenodo.org/badge/18834/openvax/mhctools.svg)](https://zenodo.org/badge/latestdoi/18834/openvax/mhctools)
-->

# mhctools

Python interface to running command-line and web-based MHC binding predictors.

## Installation

```sh
pip install mhctools
```

For MHCflurry support, also run:

```sh
mhcflurry-downloads fetch
```

## Quick start

```python
from mhctools import NetMHCpan41

predictor = NetMHCpan41(alleles=["HLA-A*02:01", "HLA-B*07:02"])

# predict for specific peptides
results = predictor.predict(["SIINFEKL", "GILGFVFTL"])

# results is a list of PeptidePreds — one per peptide
for pp in results:
best = pp.best_affinity
if best:
print(f"{best.peptide} -> {best.allele} IC50={best.value:.1f}nM")
```

## Python API

### Predicting peptides

`predict()` takes a list of peptide sequences and returns a `list[PeptidePreds]`.
Each `PeptidePreds` contains `Pred` objects for every allele and measurement
kind the predictor supports.

```python
from mhctools import NetMHCpan41

predictor = NetMHCpan41(alleles=["HLA-A*02:01", "HLA-B*07:02"])
results = predictor.predict(["SIINFEKL", "GILGFVFTL"])

pp = results[0]
pp.best_affinity # Pred with highest affinity score
pp.best_affinity.allele # "HLA-A*02:01"
pp.best_affinity.value # IC50 in nM
pp.best_affinity.score # higher = better (~0-1)
pp.best_affinity.percentile_rank # lower = better (0-100)

pp.best_affinity_by_rank # Pred with lowest percentile rank
pp.best_presentation # best EL/presentation score
pp.best_presentation_by_rank # best EL percentile rank
pp.best_stability # best pMHC stability (if available)
pp.best_stability_by_rank

# filter by kind or allele
pp.filter(kind=Kind.pMHC_affinity)
pp.filter(allele="HLA-A*02:01")
```

NetMHCpan 4.1 automatically emits both `pMHC_affinity` and `pMHC_presentation`
predictions per peptide-allele pair.

### Scanning proteins

`predict_proteins()` takes a dictionary of protein sequences and returns
`{sequence_name: list[PeptidePreds]}`:

```python
proteins = predictor.predict_proteins(
{"TP53": "MEEPQSDPSVEPPLSQETFS...", "KRAS": "MTEYKLVVVGAGGVGKS..."},
peptide_lengths=[9, 10],
)

for pp in proteins["TP53"]:
best = pp.best_affinity
if best and best.value < 500:
print(f" offset={best.offset} {best.peptide} IC50={best.value:.0f}")
```

### DataFrames

Every level has a `_dataframe` variant that flattens to a pandas DataFrame
with consistent columns:

```python
df = predictor.predict_dataframe(["SIINFEKL"], sample_name="pat001")
df = predictor.predict_proteins_dataframe({"TP53": "MEEPQ..."}, sample_name="pat001")
```

Columns: `sample_name`, `peptide`, `n_flank`, `c_flank`,
`source_sequence_name`, `offset`, `predictor_name`, `predictor_version`,
`allele`, `kind`, `score`, `value`, `percentile_rank`.

### Multi-sample predictions

`MultiSample` runs a predictor across multiple samples, each with its own
HLA genotype:

```python
from mhctools import MultiSample, NetMHCpan41

ms = MultiSample(
samples={
"pat001": ["HLA-A*02:01", "HLA-B*07:02"],
"pat002": ["HLA-A*01:01", "HLA-B*08:01"],
},
predictor_class=NetMHCpan41,
)

# {sample_name: list[PeptidePreds]}
results = ms.predict(["SIINFEKL", "GILGFVFTL"])

# {sample_name: {seq_name: list[PeptidePreds]}}
protein_results = ms.predict_proteins({"TP53": "MEEPQ..."})

# flat DataFrames with sample_name column
df = ms.predict_dataframe(["SIINFEKL"])
df = ms.predict_proteins_dataframe({"TP53": "MEEPQ..."})
```

### Measurement kinds

The `Kind` enum describes what biological quantity a `Pred` measures:

| Kind | Meaning |
|---|---|
| `pMHC_affinity` | Peptide-MHC binding affinity |
| `pMHC_presentation` | Likelihood of surface presentation (EL) |
| `pMHC_stability` | Peptide-MHC complex stability |
| `cellular_presentation` | Cross-allele presentation (e.g. MHCflurry) |
| `antigen_processing` | Combined processing score |
| `proteasome_cleavage` | Proteasomal cleavage score |
| `tap_transport` | TAP transport score |
| `erap_trimming` | ERAP trimming score |

### The Pred object

Every prediction is a frozen, self-contained `Pred` dataclass:

```python
from mhctools import Pred, Kind

pred = Pred(
kind=Kind.pMHC_affinity,
score=0.85, # ~0-1, higher = better
peptide="SIINFEKL",
allele="HLA-A*02:01",
value=120.5, # IC50 in nM
percentile_rank=0.8,
source_sequence_name="TP53",
offset=42,
predictor_name="netMHCpan",
predictor_version="4.1",
)
```

`score` is always higher-is-better. `value` is in native units (nM for
affinity, hours for stability). `percentile_rank` is always optional,
0-100, lower = stronger.

## Supported predictors

| Predictor | Kinds produced | Requires |
|---|---|---|
| `NetMHCpan` / `NetMHCpan41` | affinity + presentation | [NetMHCpan](http://www.cbs.dtu.dk/services/NetMHCpan/) |
| `NetMHCpan4` | affinity or presentation | NetMHCpan 4.0 |
| `NetMHCpan3` / `NetMHCpan28` | affinity | older NetMHCpan |
| `NetMHC` / `NetMHC3` / `NetMHC4` | affinity | [NetMHC](http://www.cbs.dtu.dk/services/NetMHC/) |
| `NetMHCIIpan` | affinity or presentation | [NetMHCIIpan](http://www.cbs.dtu.dk/services/NetMHCIIpan/) |
| `NetMHCcons` | affinity | [NetMHCcons](http://www.cbs.dtu.dk/services/NetMHCcons/) |
| `NetMHCstabpan` | stability | [NetMHCstabpan](http://www.cbs.dtu.dk/services/NetMHCstabpan/) |
| `MHCflurry` | affinity | `pip install mhcflurry` + `mhcflurry-downloads fetch` |
| `MixMHCpred` | presentation | [MixMHCpred](https://github.com/GfellerLab/MixMHCpred) |
| `RandomBindingPredictor` | affinity | (built-in) |
| `NetChop` | cleavage | [NetChop](http://www.cbs.dtu.dk/services/NetChop/) |

## Commandline examples

### Prediction for user-supplied peptide sequences
Expand All @@ -30,48 +199,27 @@ mhctools --sequence SIINFEKL SIINFEKLQ --mhc-predictor netmhc --mhc-alleles A020
mhctools --sequence AAAQQQSIINFEKL --extract-subsequences --mhc-peptide-lengths 8-10 --mhc-predictor mhcflurry --mhc-alleles A0201
```

## Python usage
## Legacy API

The old `predict_peptides()` and `predict_subsequences()` methods still work
and return `BindingPredictionCollection` objects:

```python
from mhctools import NetMHCpan
# Run NetMHCpan for alleles HLA-A*01:01 and HLA-A*02:01
predictor = NetMHCpan(alleles=["A*02:01", "hla-a0101"])

# scan the short proteins 1L2Y and 1L3Y for epitopes
protein_sequences = {
"1L2Y": "NLYIQWLKDGGPSSGRPPPS",
"1L3Y": "ECDTINCERYNGQVCGGPGRGLCFCGKCRCHPGFEGSACQA"
}

binding_predictions = predictor.predict_subsequences(protein_sequences, peptide_lengths=[9])

# flatten binding predictions into a Pandas DataFrame
df = binding_predictions.to_dataframe()

# epitope collection is sorted by percentile rank
# of binding predictions
for binding_prediction in binding_predictions:
if binding_prediction.affinity < 100:
print("Strong binder: %s" % (binding_prediction,))
predictor = NetMHCpan(alleles=["A*02:01"])
collection = predictor.predict_subsequences(
{"1L2Y": "NLYIQWLKDGGPSSGRPPPS"},
peptide_lengths=[9],
)
df = collection.to_dataframe()

for bp in collection:
if bp.affinity < 100:
print("Strong binder: %s" % bp)
```

## API

The following MHC binding predictors are available in `mhctools`:

- `MHCflurry`: open source predictor installed by default with `mhctools`, requires the user run `mhcflurry-downloads fetch` first to download MHCflurry models
- `NetMHC3`: requires locally installed version of [NetMHC 3.x](http://www.cbs.dtu.dk/services/NetMHC-3.4/)
- `NetMHC4`: requires locally installed version of [NetMHC 4.x](http://www.cbs.dtu.dk/services/NetMHC/)
- `NetMHC`: a wrapper function to automatically use `NetMHC3` or `NetMHC4` depending on what's installed.
- `NetMHCpan`: requires locally installed version of [NetMHCpan](http://www.cbs.dtu.dk/services/NetMHCpan/)
- `NetMHCIIpan`: requires locally installed version of [NetMHCIIpan](http://www.cbs.dtu.dk/services/NetMHCIIpan/)
- `NetMHCcons`: requires locally installed version of [NetMHCcons](http://www.cbs.dtu.dk/services/NetMHCcons/)
- `IedbMhcClass1`: Uses IEDB's REST API for class I binding predictions.
- `IedbMhcClass2`: Uses IEDB's REST API for class II binding predictions.
- `RandomBindingPredictor`: Creates binding predictions with random IC50 and percentile rank values.

Every binding predictor is constructed with an `alleles` argument specifying the HLA type for which to make predictions. Predictions are generated by calling the `predict` method with a dictionary mapping sequence IDs or names to amino acid sequences.
To convert legacy results to the new types:

Additionally there is a module for running the [NetChop](http://www.cbs.dtu.dk/services/NetChop) proteosomal cleavage predictor:

- `NetChop`: requires locally installed version of [NetChop-3.1](http://www.cbs.dtu.dk/services/NetChop/)
```python
preds = collection.to_preds() # list of Pred
pp_list = collection.to_peptide_preds() # list of PeptidePreds
```
10 changes: 2 additions & 8 deletions lint.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
#!/bin/bash
set -o errexit

# getting false positives due to this issue with pylint:
# https://bitbucket.org/logilab/pylint/issues/701/false-positives-with-not-an-iterable-and
python -m ruff check mhctools tests

find mhctools tests -name '*.py' \
| xargs python -m pylint \
--errors-only \
--disable=unsubscriptable-object,not-an-iterable

echo 'Passes pylint check'
echo 'Passes ruff check'
14 changes: 14 additions & 0 deletions mhctools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .binding_prediction import BindingPrediction
from .binding_prediction_collection import BindingPredictionCollection
from .pred import Pred, PeptidePreds, Kind, preds_from_rows
from .sample import MultiSample
from .iedb import (
IedbNetMHCcons,
IedbNetMHCpan,
Expand All @@ -19,6 +21,7 @@
from .netmhc_pan3 import NetMHCpan3
from .netmhc_pan4 import NetMHCpan4, NetMHCpan4_BA, NetMHCpan4_EL
from .netmhc_pan41 import NetMHCpan41, NetMHCpan41_BA, NetMHCpan41_EL
from .netmhc_pan42 import NetMHCpan42, NetMHCpan42_BA, NetMHCpan42_EL
from .netmhcii_pan import NetMHCIIpan, NetMHCIIpan3, NetMHCIIpan4, NetMHCIIpan4_BA, NetMHCIIpan4_EL, NetMHCIIpan43, NetMHCIIpan43_BA, NetMHCIIpan43_EL
from .random_predictor import RandomBindingPredictor
from .netmhcstabpan import NetMHCstabpan
Expand All @@ -27,6 +30,11 @@
__version__ = "2.2.0"

__all__ = [
"Pred",
"PeptidePreds",
"Kind",
"preds_from_rows",
"MultiSample",
"BindingPrediction",
"BindingPredictionCollection",
"IedbNetMHCcons",
Expand All @@ -50,11 +58,17 @@
"NetMHCpan4_EL",
"NetMHCpan41_BA",
"NetMHCpan41_EL",
"NetMHCpan42",
"NetMHCpan42_BA",
"NetMHCpan42_EL",
"NetMHCIIpan",
"NetMHCIIpan3",
"NetMHCIIpan4",
"NetMHCIIpan4_BA",
"NetMHCIIpan4_EL",
"NetMHCIIpan43",
"NetMHCIIpan43_BA",
"NetMHCIIpan43_EL",
"NetMHCstabpan",
"RandomBindingPredictor",
"UnsupportedAllele",
Expand Down
Loading
Loading