Skip to content

Command-line interface

The topiary command runs the whole pipeline: read an antigen source, call one or more MHC predictors, filter and rank the predictions, write a table. Every run needs an input, a predictor (or a cache) and, for allele-specific models, a list of alleles.

topiary \
  --fasta proteins.fasta \
  --mhc-predictor netmhcpan \
  --mhc-alleles HLA-A*02:01,HLA-B*07:02 \
  --ic50-cutoff 500 \
  --output-csv results.csv

Predictors and alleles

Name one or more predictors with --mhc-predictor and alleles with --mhc-alleles:

--mhc-predictor netmhcpan mhcflurry --mhc-alleles HLA-A*02:01,HLA-B*07:02

All predictors come from mhctools, which also handles their installation and licensing.

CLI name Predicts
netmhcpan affinity + presentation (auto-detects installed version)
netmhcpan4 / netmhcpan41 / netmhcpan42 specific NetMHCpan 4.x versions
netmhcpan4-ba / netmhcpan4-el single-mode (binding affinity or eluted ligand)
mhcflurry affinity + presentation + processing
mixmhcpred presentation
netmhciipan / netmhciipan4 / netmhciipan43 MHC class II
bigmhc / bigmhc-el / bigmhc-im presentation / immunogenicity
netmhcstabpan pMHC stability
pepsickle / netchop proteasomal cleavage
netmhcpan-iedb / netmhccons-iedb / smm-iedb local compatibility wrappers; require NetMHCpan 4.1 BA, NetMHCcons or SMM, respectively
random random predictions (for testing)

The historical *-iedb names run installed tools locally and have no HTTP fallback. See MHCTools' compatibility-wrapper requirements.

Peptide lengths for sliding-window scans come from the predictor unless you set them: --mhc-epitope-lengths 8,9,10,11.

Input modes

Peptides scored as given

topiary \
  --peptide-csv peptides.csv \
  --mhc-predictor netmhcpan \
  --mhc-alleles HLA-A*02:01 \
  --output-csv results.csv

The CSV needs a peptide column and may have a name column. No sliding window is applied.

Proteins scanned with a sliding window

topiary \
  --fasta proteins.fasta \
  --mhc-predictor netmhcpan \
  --mhc-alleles HLA-A*02:01,HLA-B*07:02 \
  --output-csv results.csv

Restrict the scan to parts of each sequence with --regions NAME:START-END (0-indexed, half-open):

--regions spike:319-541 nucleocapsid:0-50

Somatic variants

topiary \
  --vcf somatic.vcf \
  --mhc-predictor netmhcpan \
  --mhc-alleles HLA-A*02:01,HLA-B*07:02 \
  --filter-by "ba <= 500 | el.rank <= 2" \
  --sort-by "0.6 * affinity.descending_cdf(500, 200) + 0.4 * presentation.score" \
  --only-novel-epitopes \
  --output-csv epitopes.csv

--maf, --variant and --json-variants take the same pipeline from other variant formats. RNA expression can annotate and filter these rows.

A protein change with no genomic coordinate

Hotspots are usually named at the protein level. --protein-change GENE CHANGE takes one directly, picks a protein-coding transcript whose residue at that position matches the reference amino acid, and tiles peptides over it:

topiary \
  --protein-change EGFR T790M \
  --genome grch37 \
  --mhc-predictor netmhcpan \
  --mhc-alleles HLA-A*02:01 \
  --output-csv hotspot.csv

The variant column is empty for these rows: no genomic position was named, so none is invented. Read-level DNA/RNA evidence therefore cannot attach to them, which is why this is a separate input mode. Passing it together with --vcf, --maf, --variant or --json-variants is refused rather than mixing rows whose evidence differs. Run each mode separately and combine the results with combine_sources, which keeps each source labelled (#421).

Where several transcripts match, topiary takes the one with the longest protein sequence, breaking ties on transcript length and then name. A change can therefore land on a different transcript as the annotation release changes; pass --ensembl-release to pin it. The chosen transcript is named in the transcript_id and transcript_name columns.

Genes, transcripts and proteome-scale sources

topiary --gene-names BRAF TP53 EGFR --mhc-predictor netmhcpan --mhc-alleles HLA-A*02:01
topiary --gene-ids ENSG00000157764 --mhc-predictor netmhcpan --mhc-alleles HLA-A*02:01
topiary --transcript-ids ENST00000288602 --mhc-predictor netmhcpan --mhc-alleles HLA-A*02:01
topiary --ensembl-proteome --mhc-predictor netmhcpan --mhc-alleles HLA-A*02:01
topiary --cta --mhc-predictor netmhcpan --mhc-alleles HLA-A*02:01  # cancer-testis antigens

--cta needs the pirlygenes extra. The same sources are available in Python through topiary.sources — see the quickstart.

Filtering and ranking

--filter-by and --sort-by take ranking DSL strings:

# Keep a peptide if affinity or presentation passes
topiary \
  --fasta proteins.fasta \
  --mhc-predictor netmhcpan \
  --mhc-alleles HLA-A*02:01 \
  --filter-by "ba <= 500 | el.rank <= 2" \
  --output-csv results.csv

# Rank by a composite score from two models
topiary \
  --fasta antigens.fasta \
  --mhc-predictor netmhcpan mhcflurry \
  --mhc-alleles HLA-A*02:01 \
  --filter-by "ba <= 500" \
  --sort-by "mean(netmhcpan:affinity.logistic(350, 150), mhcflurry:affinity.logistic(350, 150))" \
  --output-csv results.csv

--ic50-cutoff, --percentile-cutoff and --presentation-cutoff are shorthand for the common single-threshold filters. For variant runs, --predict-wt scores each wildtype peptide too, which is what wt.* reads:

--predict-wt --sort-by "affinity.score - wt.affinity.score"

Exclusion filtering

Remove peptides found in reference proteomes, for tumor-specific or pathogen-specific selection:

--exclude-ensembl                    # human Ensembl proteome
--exclude-non-cta                    # non-CTA proteins (requires pirlygenes)
--exclude-tissues heart_muscle lung  # genes expressed in these tissues
--exclude-fasta reference.fasta      # custom reference sequences
--exclude-mode substring             # "substring" (default) or "exact"

Output

With no output path, the CLI prints a compact preview of the first 20 prediction rows after filtering and ranking, and reports how many rows were omitted. --subset-output-columns and --rename-output-column also apply to this preview. The summary on stderr counts prediction rows, unique peptide sequences, and named alleles in those rows; several prediction kinds can give one peptide several rows. Empty input and empty results are reported explicitly.

Explicit CSV/HTML paths save all rows and suppress the automatic preview:

--output-csv results.csv
--output-html results.html
--output-csv-sep "\t"
--subset-output-columns peptide allele affinity
--rename-output-column value ic50

--output-csv - sends every result row to stdout for a shell pipeline; progress messages, --print-columns and the summary then go to stderr:

topiary --peptide-csv peptides.csv --mhc-predictor netmhcpan41 \
  --mhc-alleles 'HLA-A*02:01' --output-csv - > results.csv

Columns

Every prediction has source_sequence_name, peptide, peptide_offset, peptide_length, allele, kind, score, value, affinity, percentile_rank and prediction_method_name.

Fragment-based predictions — predict_from_fragments and the variant-based methods, which build fragments internally — add fragment_id, source_type, overlaps_target, wt_peptide, wt_peptide_length, and any fragment-level annotations flattened to columns. With --predict-wt or TopiaryPredictor(predict_wt=True) they also add wt_value, wt_score, wt_affinity and wt_percentile_rank.

Variant predictions add variant, gene, gene_id, transcript_id, transcript_name, effect, effect_type, contains_mutant_residues, mutation_start_in_peptide and mutation_end_in_peptide.

Expression data adds columns named {prefix}_{column}, such as gene_tpm — see RNA expression.

Units follow the row's kind, which output files do not restate: value and affinity are nM for pMHC_affinity; value and score are 0–1 for pMHC_presentation and antigen_processing; percentile_rank is a percentage.

Saved files

Saved CSV/TSV and HTML files hold one row per prediction, with no index column, so pandas.read_csv(path) and topiary's own read_csv / read_tsv read them back as written. (Through 5.81.0 the row number was saved under the header #, which readers that treat # as a comment — including topiary's — took for a metadata line.)

Floats are written to six significant digits: a predictor reporting 6.296 had been saved as 6.296000000000002. The digits past the sixth come from binary floating point, not from the model. Values keep their decimal point, so a reloaded measurement column is still a float.

Row order is the ranking when --sort-by is given. Otherwise it follows each source sequence's windows in position order (peptide_offset ascending), with source sequences in the order they first appear; a peptide list keeps its input order. With --sort-by, an output_row column numbers the rows from 1 so the order survives re-sorting in a spreadsheet. It is a row position in this file, not a per-candidate rank — see rank_candidates and candidate_rank for that.

HTML output is a complete page, and a missing value is an empty cell whatever its type.

Exit status

Status Meaning
0 Success
1 Something topiary found after reading the command: a missing or unreadable input, an allele a predictor rejects, a missing optional dependency, a predictor that isn't set up, an output that can't be written. One topiary: error: line on stderr.
2 The command line is malformed: an unknown flag, a bad value, required flags missing or in conflict. The error follows the usage text.
3 Partial result under --cache-miss-report (see cached predictions).

Output paths, --mhc-predictor-path and the inputs are checked before any predictor is loaded, so these fail in the first second rather than after the run.

Installation details

pip install topiary requires Python 3.10 or newer and brings in Osteosarc and its read-extraction dependency, which supply shared source data and indexed regional read extraction; see shared Osteosarc data. Importing topiary does not acquire any data.

MHC predictors are installed separately and called through mhctools: MHCflurry with pip install mhcflurry && mhcflurry-downloads fetch, NetMHCpan and the other DTU tools under a license from DTU Health Tech.

Variant annotation, gene lookups and SelfProteome need a local Ensembl annotation database. Install the release your PyEnsembl selects by default:

pyensembl install --release "$(python -c 'from pyensembl import ensembl_grch38; print(ensembl_grch38.release)')" --species human

Installing some other release does not change that default — select it explicitly with --ensembl-release or the API's release= argument. GRCh37 inputs need release 75.

Two extras add input sources:

  • pip install 'topiary[pirlygenes]' — cancer-testis antigens and tissue expression (--cta, --exclude-non-cta, --exclude-tissues).
  • pip install 'topiary[isovar]' — protein fragments assembled from RNA reads. The extra requires Isovar 1.39.5 or newer, which Topiary also checks at run time, and works with the Osteosarc 0.14 and 0.15 series. Isovar is not needed for any other input source; see the consumer guide for the version rules.