-
Notifications
You must be signed in to change notification settings - Fork 0
Mapping
prot2exon map projects protein-domain amino-acid coordinates onto a transcript's genomic CDS / UTR / intron structure. You give it a query BED and an index (or GTF), pick how you want the answer shaped with --output, and it writes a set of TSVs and BEDs describing where the domain lives in the genome.
prot2exon reads whitespace-separated, BED-like text. Lines starting with # and blank lines are ignored.
# rows with a domain (ENSP or ENST, with or without version)
ENSP00000269305 10 50 AD1 TF1
ENST00000269305.9 10 50 AD1_ENST TF1 # same answer as the line above
# row without a domain (whole-transcript structure only)
ENSP00000418960
| # | Required | Meaning |
|---|---|---|
| 1 | yes |
id — ENSP* (protein) or ENST* (transcript). Versioned or unversioned; the version suffix is stripped on both sides before lookup. RefSeq (NP_*, NM_*) and custom IDs also work as long as the GTF/index has them. |
| 2 | no |
aa_start — 1-based inclusive. Omit (or set to 0) for no-domain mode. |
| 3 | no |
aa_end — 1-based inclusive. |
| 4 | no |
domain_id (used as input_id for tracking through the outputs). |
| 5+ | no | Ignored — free space for human-readable metadata. |
If column 4 is missing but a domain is present, input_id falls back to id:aa_start-aa_end; in no-domain mode it falls back to id. Either way every row stays uniquely identifiable in the outputs.
An ENST resolves to the same intervals as its matching ENSP, so the two produce identical mapping output; the summary's input_id_type column records which form you supplied. A non-coding ENST (no CDS in the GTF) is reported in unmapped_domains.tsv with reason = no_CDS_for_protein. A row that is just an id with no aa range is processed in no-domain (structure-only) mode: the overlap columns are NA and the companion BEDs are empty, but isoform_structure.tsv is still fully populated so the transcript can be plotted as-is.
To build query BEDs from domain databases (InterProScan, UniProt feature tables, HMMER/Pfam), see the prepare helpers on the Python API page. The index that map reads is built with prot2exon index — see Index.
Every --output mode describes the same mapping; they are complementary views of where a domain sits in the genome. Pick the one that answers your question, or use the default all to get everything (see Output description).
Answers which CDS exons code the domain? — every CDS exon of the transcript, classified by whether it overlaps the domain.
| File | Contents |
|---|---|
domain_cds_segments.tsv |
one row per CDS exon of the transcript, each with an overlaps_domain column |
domain_cds_segments.bed |
subset: only the CDS exons that code the domain (coding_overlap) |
prot2exon map --index human.idx --bed q.bed --out-dir results --output codingimport prot2exon as p2e
mapper = p2e.Mapper(index="human.idx")
result = mapper.map_batch(
[{"protein_id": "ENSP00000269305", "aa_start": 102, "aa_end": 292, "domain_id": "TP53_DBD"}],
output="coding",
)
result.cds_segments # DataFrame of CDS rowsoverlaps_domain is coding_overlap for a CDS exon that codes the domain and no otherwise (NA in no-domain mode); partial-overlap rows also report the coding sub-interval in genomic / CDS-nt / aa space. Full column layout in Output description.
Answers which introns fall inside the domain's genomic span? — every intron of the transcript, classified by whether it lies within the domain envelope.
| File | Contents |
|---|---|
domain_introns.tsv |
one row per intron of the transcript, each with an overlaps_domain column |
domain_introns.bed |
subset: only the introns inside the domain span (inside_domain_genomic_span) |
prot2exon map --index human.idx --bed q.bed --out-dir results --output intronsresult = mapper.map_batch(queries, output="introns")
result.introns # DataFrame of intron rowsoverlaps_domain is inside_domain_genomic_span for an intron between two domain-coding CDS exons, no for an intron elsewhere, and NA in no-domain mode. Full schema in Output description.
Answers what is the single genomic envelope of the domain, introns included? — one interval per domain, from its first coding base to its last.
| File | Contents |
|---|---|
domain_span_with_introns.bed |
one row per domain: first → last coding base, spanning any introns in between |
prot2exon map --index human.idx --bed q.bed --out-dir results --output spanresult = mapper.map_batch(queries, output="span")
result.span_bed # DataFrame, one row per domainSee Output description for the BED column meanings.
--bed12 is an add-on flag, not an output mode: it writes one extra file, domain_blocks.bed12, in addition to whatever --output you chose. Use it to get an IGV-ready BED12 alongside a narrower view such as coding. It is a no-op under --output all or --output bed12, which already write the file.
| File | Contents |
|---|---|
domain_blocks.bed12 |
one IGV/UCSC-ready BED12 row per domain — the domain envelope drawn thick, with the coding CDS slices as blocks and the in-domain introns as gaps |
prot2exon map --index human.idx --bed q.bed --out-dir results --output coding --bed12# The Python results carry the bed12 rows whenever they're produced:
result = mapper.map_batch(queries, output="all")
result.bed12 # DataFrame, one row per domainField-by-field meaning in Output description.
--output isoform writes the plot-ready isoform_structure.tsv: one row per structural feature of the transcript — five_prime_UTR, CDS, three_prime_UTR, and inferred intron rows. CDS exons that the domain only partially covers are split into separate rows (the overlapping and non-overlapping portions), and every row carries a plot_group string for direct colour mapping.
prot2exon map --index human.idx --bed q.bed --out-dir results --output isoformresult = mapper.map_batch(queries, output="isoform")
result.isoform # DataFrame, one row per structural featureThis is the table you feed to a renderer; see Plotting for turning it into a transcript diagram. The column layout (including feature_id / feature_part and the plot_group values) is in Output description.
domain_mapping_summary.tsv is written for every command (one row per input query, mapped or not), and unmapped_domains.tsv appears whenever at least one query fails. The per-feature TSVs and BEDs depend on --output. This section documents every file's schema.
--output selects which feature files are produced; the default is all.
--output |
Question | TSV(s) | BED(s) |
|---|---|---|---|
coding |
Where are the CDS exons, and which code the domain? | domain_cds_segments.tsv |
domain_cds_segments.bed |
introns |
Where are the introns, and which lie in the domain span? | domain_introns.tsv |
domain_introns.bed |
span |
What is the domain's genomic envelope (introns included)? | — | domain_span_with_introns.bed |
isoform |
How is the whole transcript organised? | isoform_structure.tsv |
— |
bed12 |
One IGV-ready BED12 row per domain. | — | domain_blocks.bed12 |
all (default) |
Everything above. | all 4 TSVs | all 4 BEDs |
all additionally writes run_metadata.json. The --bed12 flag adds domain_blocks.bed12 to any of the first four modes. Regardless of mode, domain_mapping_summary.tsv is always written and unmapped_domains.tsv is written on any failure.
| Output | System |
|---|---|
*.bed |
0-based half-open (BED standard). |
*.tsv |
1-based inclusive (matches GTF). |
NA means not applicable to this row — e.g. CDS-nt fields on a UTR row, or overlap fields in no-domain mode. The BED and the TSV describe the same intervals in different conventions, which is why a coordinate can differ by 1 between them: a CDS at GTF 7676219..7676272 (1-based inclusive, length 54) is BED 7676218..7676272 (0-based half-open, still length 54) — end − start matches, only start shifts.
One row per input query, written for every --output mode.
| Column | Type | Meaning |
|---|---|---|
input_id |
string | User-supplied identifier |
protein_id |
string | Normalised (version suffix stripped) |
transcript_id |
string | Transcript that this protein belongs to |
gene_id |
string | Ensembl gene id |
gene_name |
string | HGNC-style gene symbol |
domain_id |
string | BED column 4 (if any) |
chrom, strand
|
Chromosome and strand of the transcript | |
aa_start, aa_end
|
int / NA | Input domain bounds; NA in no-domain mode |
domain_length_aa |
int / NA | aa_end − aa_start + 1 |
domain_length_nt |
int / NA | domain_length_aa × 3 |
protein_length_aa |
int | Total CDS length / 3 for this protein |
domain_genomic_start / _end
|
int / NA | Genomic envelope of the domain |
n_coding_segments |
int / NA | Number of CDS exon slices the domain spans |
fully_mapped |
bool |
true if the entire aa range fits inside the CDS |
no_domain_mode |
bool |
true if the BED row had no aa range |
input_id_type |
ENSP / ENST / NA
|
Which kind of id the user supplied |
is_mane_select |
true / false / NA
|
MANE Select transcript? NA if the GTF lacks tag attributes. |
is_ensembl_canonical |
true / false / NA
|
Ensembl_canonical transcript? NA if the GTF lacks tag attributes. |
cds_length_mismatch |
bool |
true if sum(CDS_nt) % 3 != 0 (Sec, readthrough, incomplete) |
cds_nt_remainder |
int (0, 1, 2) | sum(CDS_nt) % 3 |
n_coding_exons_touched |
int / NA | Distinct CDS exons the domain overlaps (split CDS rows count once) |
n_introns_spanned |
int / NA | Introns between two domain-coding CDS rows |
is_single_exon_domain |
bool / NA | n_coding_exons_touched == 1 |
fraction_domain_in_largest_exon |
float / NA | Max over CDS exons of summed domain_overlap_fraction_of_domain; in [0, 1] |
intron_burden_nt |
int / NA | Sum of lengths of the introns counted in n_introns_spanned
|
status |
string |
ok / ok_cds_mismatch / partial / partial_cds_mismatch / structure_only / unmapped reason |
All three share the same column layout — they differ only in which feature types they include:
| File | Rows |
|---|---|
domain_cds_segments.tsv |
every CDS row |
domain_introns.tsv |
every intron row |
isoform_structure.tsv |
every 5′UTR / CDS / 3′UTR / intron row |
You can therefore cat or join them on input_id / feature_id interchangeably.
Identity columns: input_id, gene_id, gene_name, transcript_id, protein_id, domain_id.
Location columns:
| Column | Meaning |
|---|---|
chrom |
Chromosome |
strand |
+ or −
|
feature_genomic_start / _end
|
1-based inclusive |
feature_length_nt |
end − start + 1 |
Feature-type columns:
| Column | Meaning |
|---|---|
feature_type |
One of five_prime_UTR, CDS, three_prime_UTR, intron
|
feature_id |
Stable across splits. Numbered in translation order: CDS_1 is the most 5′ CDS, then CDS_2, etc. UTRs and introns are numbered separately (five_prime_UTR_1, intron_1, …) |
feature_part |
1..K when a CDS row was split by partial domain overlap; pieces of the same original CDS share the same feature_id, differ by feature_part. Always 1 for UTR / intron rows |
exon_number |
Source GTF exon_number for UTR / CDS rows; NA for introns |
A single GTF CDS exon can be split into multiple rows when the domain only covers part of it. The pieces always share the same feature_id; only feature_part differs. For example, when a domain ends in the middle of CDS_2 (translation order):
| feature_id | feature_part | start | end | overlaps_domain |
|---|---|---|---|---|
| CDS_2 | 1 | 75278995 | 75279120 | coding_overlap |
| CDS_2 | 2 | 75279121 | 75279123 | no |
To re-aggregate the full original CDS, group by (input_id, feature_id). To plot the overlap shape, fill by plot_group and ignore feature_part.
Ordering columns:
| Column | Meaning |
|---|---|
feature_order_genomic |
1..N along the chromosome (low → high coord). |
feature_order_transcript |
1..N in translation direction. Equals feature_order_genomic on + strand; reversed on − strand. |
For − strand genes, plot using feature_genomic_start/end on the X axis (genomic coords), but use feature_order_transcript to interpret biological order (5′ → 3′ of the protein).
CDS-coordinate columns (NA on UTR / intron rows):
| Column | Meaning |
|---|---|
cds_nt_start / cds_nt_end
|
CDS-relative nt offsets (1-based) of this slice's first/last base |
aa_start_encoded / aa_end_encoded
|
First / last aa that this slice encodes (1-based) |
The mapping is aa = ⌈cds_nt / 3⌉. A 1-nt CDS slice containing only the third base of an aa still reports that aa.
Domain-overlap columns. overlaps_domain is not a yes/no flag — it discriminates coding overlap from intronic overlap inside the domain envelope:
| Value | Meaning |
|---|---|
no |
The row is outside the domain entirely. UTR rows always carry no
|
coding_overlap |
CDS row whose genomic interval overlaps a domain-coding range |
inside_domain_genomic_span |
Intron located between two coding_overlap CDS rows |
NA |
No-domain query (no domain to compare against) |
The companion columns are filled only for coding_overlap rows:
| Column | Meaning |
|---|---|
domain_overlap_genomic_start / _end
|
Sub-interval of this row that codes the domain (1-based inclusive) |
domain_overlap_cds_nt_start / _end
|
Same overlap projected to CDS-nt (1-based) |
domain_overlap_aa_start / _end
|
Same overlap projected to aa |
domain_overlap_fraction_of_feature |
overlap_length / feature_length_nt |
domain_overlap_fraction_of_domain |
overlap_length / domain_length_nt. Sums to 1.0 across all coding_overlap rows of the same domain |
Plotting column. plot_group is a single string suitable for direct colour mapping. With a domain: five_prime_UTR / three_prime_UTR, CDS_no_domain (CDS outside the domain), CDS_domain (CDS that encodes the domain), intron (outside the span), intron_domain_span (intron between two CDS_domain rows). In no-domain mode it is just the feature type (five_prime_UTR / CDS / three_prime_UTR / intron).
All three are 6-column standard BED (chrom, start_0based, end, name, score=0, strand).
| File | Rows |
|---|---|
domain_cds_segments.bed |
CDS rows with overlaps_domain == coding_overlap
|
domain_introns.bed |
intron rows with overlaps_domain == inside_domain_genomic_span
|
domain_span_with_introns.bed |
one row per domain (genomic envelope, introns included) |
name is protein_id[_domain_id]_<aa_start>-<aa_end>. No-domain queries contribute zero rows to any companion BED.
One BED12 row per domain. The whole feature is drawn thick in IGV; blocks are the CDS slices that code the domain. Empty in no-domain mode.
| BED12 field | Meaning here |
|---|---|
chrom |
chromosome |
chromStart (0-based) |
first base of the domain genomic envelope |
chromEnd |
last base + 1 of the envelope (so end − start = envelope length, introns included) |
name |
protein_id[_domain_id]_<aa_start>-<aa_end> |
score / strand
|
0 / transcript strand |
thickStart / thickEnd
|
equal to chromStart / chromEnd — IGV draws the whole feature thick |
itemRgb |
255,0,0 (red) |
blockCount |
number of CDS slices that code the domain (coding_overlap rows) |
blockSizes / blockStarts
|
comma-separated, in genomic order (starts are offsets from chromStart) |
Written only when at least one query failed.
| Column | Meaning |
|---|---|
input_id, protein_id, aa_start, aa_end, domain_id
|
identity (protein_id = NA for non-coding ENST queries) |
reason |
one of: protein_not_in_index, no_CDS_for_protein, domain_beyond_protein_length, no_overlap
|
Written only with --output all. Records tool / version / timestamp_utc, output_kind, annotation_source (the GTF or index used), index_format_version, coordinate_conventions, query_counts ({ total, mapped, unmapped, no_domain_mode }), and cli (the full invocation).
GENCODE GTFs (since v34) carry tag "MANE_Select" and tag "Ensembl_canonical". The tool surfaces these in is_mane_select / is_ensembl_canonical (true / false / NA) on domain_mapping_summary.tsv and every feature TSV. NA is reserved for GTFs that carry no tag attribute anywhere (typical of base Ensembl) — distinct from false ("tags exist, but not on this transcript"). Filter to MANE Select queries with:
awk -F'\t' 'NR==1 || $20=="true"' domain_mapping_summary.tsv # col 20 = is_mane_selectThe tool maps every query but flags the rare cases where the CDS isn't a multiple of 3: cds_length_mismatch (true if sum(CDS_nt) % 3 != 0), cds_nt_remainder (0/1/2), and a _cds_mismatch suffix on status. This catches selenoproteins (internal TGA re-coded as Sec, plus a C-terminal extension), stop-codon readthrough, and incomplete cds_start_NF / cds_end_NF transcripts. The aa↔nt math uses ceiling division (aa = ⌈cds_nt / 3⌉), so a domain at the very C-terminus of an incomplete CDS may be clipped by 1–2 aa; the flag is your hint to check.
A codon that straddles an exon boundary is mapped correctly in both phases (1+2 and 2+1) — the math is cumulative-nt-based, not exon-by-codon. For a domain on the split aa, both the upstream and downstream CDS slices report a coding_overlap row, the intron between them is marked inside_domain_genomic_span, and the two domain_overlap_fraction_of_domain values (≈1/3 and ≈2/3) sum to 1.0. Two synthetic cases in tests/ exercise this.
Input BED:
ENSP00000306245 5 100 RD1 TF2
ENSP00000306245 is a + strand transcript; domain RD1 (aa 5..100) spans the end of one CDS slice and most of the next. The relevant rows of isoform_structure.tsv (selected columns):
| feature_type | feature_id | feature_part | feature_genomic_start | feature_genomic_end | aa_start_encoded | aa_end_encoded | overlaps_domain | plot_group |
|---|---|---|---|---|---|---|---|---|
| CDS | CDS_1 | 1 | 75278983 | 75278994 | 1 | 4 | no | CDS_no_domain |
| CDS | CDS_2 | 1 | 75278995 | 75279123 | 5 | 47 | coding_overlap | CDS_domain |
| intron | intron_1 | 1 | 75279124 | 75279876 | NA | NA | inside_domain_genomic_span | intron_domain_span |
| CDS | CDS_3 | 1 | 75279877 | 75280035 | 48 | 100 | coding_overlap | CDS_domain |
| CDS | CDS_4 | 1 | 75280036 | 75280128 | 101 | 131 | no | CDS_no_domain |
| three_prime_UTR | three_prime_UTR_1 | 1 | 75281422 | 75282230 | NA | NA | no | three_prime_UTR |
To plot, fill by plot_group; to highlight the domain, take plot_group ∈ {CDS_domain, intron_domain_span}; to get just the domain-coding bases, take domain_cds_segments.bed. The same id on its own line (no aa columns) emits the whole-transcript rows with overlaps_domain = NA.
1 - How to install
2 - Building an index
(fastCDS index, fastCDS fetch)
3 - Mapping
(fastCDS map)
4 - Plotting
(fastCDS plot)
6 - Performance and benchmarking
7 - Reference