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. Quote allele lists: * is a glob character, and shells that do not pass unmatched patterns through (zsh, and bash under failglob) reject the command before topiary sees it.

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 and variant-expression evidence therefore cannot attach to them. Gene- and transcript-expression evidence can attach because the selected transcript supplies those identities.

Protein changes can be named beside --vcf, --maf, --variant or --json-variants in one run. The output column mutation_input is genomic_variant for coordinate-bearing rows and protein_change for direct changes. This column is available to --filter-by and --sort-by, so a filter requiring variant-only evidence can state what should happen to protein changes:

--variant-expression isovar.tsv \
--filter-by "mutation_input == 'protein_change' | variant_num_alt_reads >= 3"

Every configured mutation shares one effect-selection, prediction, filter and sort pass. Distinct --protein-change flags remain independent inputs; they are not treated as transcript alternatives merely because all lack a genomic variant (#421). Exact duplicate fragments still follow the pipeline's normal deduplication.

Where several transcripts carry the named reference residue, topiary picks between them with representative_transcript, the same rule the gene sources use: complete protein-coding translations first, then standard amino-acid sequences, then length, with transcript-ID tie breaking. 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 uses the included PirlyGenes integration. The same sources are available in Python through topiary.sources — see the quickstart.

Source species, reference backgrounds and MHC

--species selects the antigen sequence source. Human is the default for direct inputs; a non-human built-in source requires --ensembl-release. Species names and aliases follow PyEnsembl (mouse and Mus musculus select the same species). --mhc-alleles is independent: using mouse proteins with a human HLA allele is valid, including HLA-transgenic models (Pascolo et al., PMID 9182675).

# Mouse proteins presented by mouse MHC
topiary --gene-names Trp53 --species mouse --ensembl-release 102 \
  --mhc-predictor netmhcpan --mhc-alleles 'H2-Kb'

# The same source with human HLA, for an HLA-transgenic model
topiary --gene-names Trp53 --species mouse --ensembl-release 102 \
  --mhc-predictor netmhcpan --mhc-alleles 'HLA-A*02:01'

# Human antigens with an explicitly selected mouse comparison background
topiary --gene-names TP53 --species human \
  --exclude-ensembl --exclude-species mouse --exclude-ensembl-release 102 \
  --mhc-predictor netmhcpan --mhc-alleles 'HLA-A*02:01'

Built-in exclusions normally inherit the source species and release. For variant inputs, this comes from the annotations actually loaded, including references in MAF and JSON files. --genome still chooses variant coordinates; it is rejected for direct sequence inputs, where --species / --ensembl-release select the source. For protein changes, either form can select the annotation. If source species/release flags accompany an annotated input, they must agree with it.

--exclude-species starts an independent reference context; its release is --exclude-ensembl-release, never a number borrowed from another species. Explicit human references may use the current human default; non-human references require a release. --exclude-ensembl-release alone keeps the inferred source species and overrides its background release. Mixed-species annotations, multiple annotation releases and custom annotations without a numbered Ensembl release require an explicit background when using built-in exclusions. They remain usable without that ambiguous built-in comparison.

FASTA/CSV inputs carry no inferred species metadata: choose --species and a release explicitly when they should determine a non-human built-in background. Additional --exclude-fasta files can supply other backgrounds; all their sequences are retained even if different files reuse a header. Combining them excludes matches to the union. Run separate comparisons if you need separate human and mouse results. Exclusions are sequence matching, not a simulation of host antigen processing or transplant immunity.

--cta is human-only in the antigen-source context. --exclude-non-cta and --exclude-tissues are human-only in the background context. Human CTA antigens with a mouse background are valid; requesting mouse CTA membership or applying the human tissue-expression table as mouse data gives an error.

Before 5.112, built-in exclusions defaulted to human even for mouse variants. They now follow the actual input annotation. To request a different reference deliberately, use the exclusion-specific options instead of changing source flags.

A gene usually has several Ensembl translations. All gene-derived inputs use best_protein_transcript: complete protein-coding translations first, then standard amino-acid sequences, then length, with transcript-ID tie breaking. --protein-change applies that same ranking, through the shared representative_transcript, to the transcripts carrying its reference residue.

By default, Topiary skips protein scan inputs containing X (case-insensitive), including all their clean windows, and warns with a count and example source names. Other proteins continue normally; if every input is excluded, the command succeeds with an empty prediction table. This shared policy covers CTA, gene, tissue, FASTA, transcript, proteome and variant/fragment scans. An excluded WT baseline leaves WT prediction values missing without removing the candidate. It acts on the actual scan input, after region or variant-fragment extraction.

Use --protein-x-policy windows (Python: protein_x_policy="windows") to process the usable parts of these proteins. Only windows whose peptide and required model flanks contain no X are scored. Names and offsets still refer to the original protein. An internal X never becomes a new terminus, and regions on either side are never concatenated. For example, a 9-residue window in XASIINFEKLGGG can start at offset 3 or 4 with a model requiring two residues of context on each side; shorter flanks are allowed only at the true boundary. Models without flanking inputs can use more windows.

The model or cache must declare its required context. Unknown limits cause the X-containing inputs to be skipped with a warning; clean proteins continue. Stored predictions retain their original context and values. Missing retained cache windows still raise unless --cache-miss-report requests partial results; see cached predictions.

--protein-x-policy allow explicitly passes original proteins unchanged to a model that supports X, retaining strict producer errors. Use windows for the X-free override. Other residues, exact peptide/peptide-occurrence inputs and reference exclusion sets retain their existing validation and semantics.

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                    # source annotation's 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 broken dependency installation, 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.11 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. Topiary uses PyEnsembl's default GRCh38 release, which is Ensembl 115 in PyEnsembl 2.32.3:

pyensembl install --release 115 --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.

The normal install includes cancer-testis antigen and tissue-expression sources through PirlyGenes/OncoRef (--cta, --exclude-non-cta, --exclude-tissues), RNA assembly through Isovar, and WEIRDO foreignness. No Topiary installation extras are needed. See the consumer guide for the Isovar version rules.

PirlyGenes 6.0.18 currently pins OncoRef 1.19.2, so the compatible installation uses that release rather than the newer OncoRef 1.21.0. Adoption of the newer owner data is tracked in PirlyGenes #655. Reference genomes, model assets and licensed executables remain explicit inputs; installing Topiary does not download them.