Transcript Mapping
Strand-aware, locus-based mapping of GT to prediction transcripts.
This module provides the core logic for associating ground-truth (GT) and predicted transcripts from GFF/GTF files using a gffcompare-style algorithm:
Locus grouping — GT transcripts are clustered into loci (sets of mutually reachable overlapping transcripts) by an O(n log n) coordinate sweep.
Junction-F1 scoring — every predicted transcript in a locus is scored against all GT transcripts using a continuous junction-F1 metric:
2 × |shared junctions| / (|pred junctions| + |ref junctions|). Single-exon fallback: reciprocal overlap fraction.Optimal 1:1 assignment — the Hungarian algorithm (
scipy.optimize.linear_sum_assignment) maximises the total junction-F1 across all (GT, pred) pairs in a locus. Each GT transcript and each prediction is assigned at most once per predictor.
Two locus matching modes are supported (see LocusMatchingMode):
FULL_DISCOVERY — maximise the number of 1:1 matches per locus. Unmatched GT transcripts yield null-prediction FN pairs; unmatched predictions yield null-GT FP pairs. Best for multi-isoform tools.
BEST_PER_LOCUS — keep only the single highest-scoring (GT, pred) pair per locus and drop all others. Best for single-transcript tools (e.g. Augustus).
Public API
- class gene_calling_benchmark.transcript_mapping.LocusMatchingMode(*values)[source]
-
Controls how GT and prediction transcripts are paired within a locus.
- FULL_DISCOVERY
All GT transcripts participate; the optimal set of 1:1 matches (maximising total junction-F1) is computed via the Hungarian algorithm. Unmatched GT transcripts contribute null-prediction FN pairs; unmatched predictions contribute null-GT FP pairs.
Use this mode for multi-isoform tools (Helixer, SegmentNT, …).
- BEST_PER_LOCUS
Every GT locus is scored exactly once per predictor, so a predictor is never rewarded for emitting more isoforms nor penalised for emitting fewer than the reference. The per-transcript denominator is therefore the full set of GT loci and is directly comparable across predictors. Each (locus, predictor) pair resolves to one of three entries:
Case A — match. The predictor’s single highest-scoring (GT, pred) pair in the locus is kept; all other isoforms and predictions in that locus are dropped.
Case C — overlap, wrong structure. The predictor matched nothing in the locus but has a transcript overlapping it (no shared junction). That prediction is paired against the real GT isoform it best overlaps (
junction_f1 = 0), so a confidently-wrong call is penalised on its coinciding bases rather than silently dropped.Case B — clean miss. The predictor has no transcript overlapping the locus at all: a locus-level FN against the representative (longest) isoform, paired with a null prediction.
Use this mode for single-transcript tools (Augustus, …).
Each predictor accumulates independently, and Case C only fires for a predictor that matched nothing in the locus, so the GT isoform it scores against is never also counted in a Case A entry for that same predictor — no GT base is double-counted within a predictor’s per-transcript numbers.
Precision note: predictions that overlap no GT locus become intergenic FP entries (null GT). But an extra prediction that overlaps a locus the predictor already matched (Case A) is dropped from the per-transcript view — counting it would force a second entry for that locus and double-count the GT bases the matched pair already covers. Those dropped bases are still charged by the global
nucleotideprecision (union over all predicted bases), so per-transcript precision can read slightly optimistically relative to the global numbers; pair the two views. Scoring every overlapping isoform is whatFULL_DISCOVERYis for.
- class gene_calling_benchmark.transcript_mapping.MatchClass(*values)[source]
-
Structural relationship between a predicted and a GT transcript.
Listed in priority order (highest quality first).
- EXACT = 'exact'
Identical intron chains — every splice site matches exactly.
- CONTAINED = 'contained'
Pred intron chain ⊆ GT intron chain and pred span ⊆ GT span.
- CONTAINS = 'contains'
GT intron chain ⊆ pred intron chain and GT span ⊆ pred span.
- SHARED_JUNCTION = 'shared_junction'
At least one shared splice junction between GT and prediction.
- OVERLAPPING = 'overlapping'
Coordinate overlap only — no shared junctions (includes single-exon).
- class gene_calling_benchmark.transcript_mapping.PredictionMatch(**data)[source]
Bases:
BaseModelA single predicted transcript matched to a GT transcript.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
- class gene_calling_benchmark.transcript_mapping.TranscriptMapping(**data)[source]
Bases:
BaseModelAssociation between one GT transcript and its matched predictions.
- Parameters:
- gt_id
Ground-truth transcript identifier. For unmatched predictions this starts with
_UNMATCHED_PRED_PREFIX.- Type:
- gt_start, gt_end
Genomic coordinates of the evaluation window (1-based, inclusive).
- Type:
- is_unmatched_prediction
Truewhen no GT transcript was assigned to this prediction. The GT array is null (all-background) for unmatched predictions.- Type:
- matched_predictions
Per-predictor matches. Empty when no predictor was matched to this GT transcript.
- Type:
- fn_for_predictors
When set, this is a
BEST_PER_LOCUSCase-B (clean miss) entry that counts as a miss only for the named predictor(s) — those with no prediction overlapping the locus. Other predictors skip it, so every GT locus is counted once per predictor without charging a predictor that matched (Case A) or overlapped (Case C) the same locus.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- gene_calling_benchmark.transcript_mapping.map_transcripts(gt_path, pred_paths, label_config=None, *, gt_df=None, pred_dfs=None, transcript_types=None, gt_feature_role_map=None, pred_feature_role_maps=None, exclude_features=None, locus_matching_mode=LocusMatchingMode.FULL_DISCOVERY)[source]
Map GT transcripts to predicted transcripts across multiple predictors.
Uses a gffcompare-style locus-based algorithm:
Group GT transcripts into loci by coordinate overlap.
Score each predicted transcript against all GT transcripts in the same locus using continuous junction-F1.
Assign 1:1 pairings via the Hungarian algorithm (FULL_DISCOVERY) or take the single best pair (BEST_PER_LOCUS).
- Parameters:
gt_path (
str|Path) – Path to the ground-truth GFF/GTF file.pred_paths (
dict[str,str|Path]) –{predictor_name: path}for each prediction file.label_config (
LabelConfig|None) – Label semantics used for later array construction. Needed here so feature-role maps can be validated against the active annotation mode. When omitted, mapping falls back toEXON_INTRONdefaults.gt_df (
DataFrame|None) – Pre-parsed GT GFF DataFrame. When given, gt_path is not re-parsed (avoids a redundant parse when the caller already has the DataFrame).pred_dfs (
dict[str,DataFrame] |None) – Pre-parsed prediction DataFrames keyed by predictor name. When given, pred_paths is not re-parsed.transcript_types (
list[str] |None) – Feature types that define transcript boundaries. Defaults toDEFAULT_TRANSCRIPT_TYPES.gt_feature_role_map (
dict[str,str] |None) – Maps GT GFF/GTF feature types to benchmark roles. WhenNone, the mode-specific default is used.pred_feature_role_maps (
dict[str,str] |dict[str,dict[str,str]] |None) – Per-predictor feature-role maps.Nonemeans use the GT role map.exclude_features (
list[str] |None) – Feature types to ignore entirely (e.g.["gene"]).locus_matching_mode (
LocusMatchingMode) – Assignment strategy.FULL_DISCOVERY(default) maximises the number of 1:1 matches per locus.BEST_PER_LOCUSkeeps only the single best-scoring pair per locus and drops the rest.
- Returns:
In FULL_DISCOVERY mode: one entry per GT transcript (including unmatched) plus sentinel entries for unmatched predictions. In BEST_PER_LOCUS mode: only matched pairs are returned.
- Return type:
- gene_calling_benchmark.transcript_mapping.build_paired_arrays(mapping, gt_df, pred_dfs, label_config, transcript_types=None, gt_feature_role_map=None, pred_feature_role_maps=None, *, _gt_index=None, _pred_indices=None, _gt_role_map=None, _pred_role_maps=None)[source]
Build the GT and per-predictor annotation arrays for one mapping.
GT array
Matched GT transcript → built from that transcript’s child features.
Unmatched prediction (
is_unmatched_prediction=True) → null array (all-background), representing a pure FP with no reference signal.
Prediction arrays
Predictor has a match → built from the matched prediction’s child features (transcript-specific, not a region union).
Predictor has no match for this GT → null array (all-background), representing a missed transcript.
Using transcript-specific arrays (rather than region-based union) ensures that each matched pair is evaluated in isolation: coverage from other transcripts in the same locus does not contaminate either array.
- Parameters:
mapping (
TranscriptMapping) – A single mapping entry frommap_transcripts().gt_df (
DataFrame) – Pre-collected GT GFF DataFrame.pred_dfs (
dict[str,DataFrame]) –{predictor_name: DataFrame}for each prediction file.label_config (
LabelConfig) – Label configuration defining integer tokens.transcript_types (
list[str] |None) – Feature types that denote transcript boundaries.gt_feature_role_map (
dict[str,str] |None) – Maps GT GFF/GTF feature types to benchmark roles. WhenNone, the mode-specific default is used.pred_feature_role_maps (
dict[str,str] |dict[str,dict[str,str]] |None) – Per-predictor feature-role maps.Nonemeans every predictor uses the GT role map._gt_index (dict[tuple[str, str], list[tuple[str, int, int]]] | None)
_pred_indices (dict[str, dict[tuple[str, str], list[tuple[str, int, int]]]] | None)
- Returns:
(gt_array, {predictor_name: pred_array}). The dict contains an entry for every predictor in pred_dfs.- Return type:
Notes
The leading-underscore keyword parameters are a private fast path for callers that build arrays in a loop (e.g. the pipeline): pre-built
(seqid, parent)indices and pre-normalized role maps are reused across mappings instead of being rebuilt on every call. Stand-alone callers omit them and the per-call build/normalize path runs as before.
- gene_calling_benchmark.transcript_mapping.export_mapping_table(mappings, output_path)[source]
Write the mapping list to a human-readable TSV for debugging.
Columns:
seqid,strand,gt_id,gt_start,gt_end,is_unmatched_prediction,predictor,pred_id,pred_start,pred_end,match_class,base_overlap,junction_f1.Each row represents one GT-prediction pair. GT transcripts with multiple matched predictions produce multiple rows. GT transcripts with no matches produce a single row with empty prediction columns.
- Parameters:
mappings (
list[TranscriptMapping]) – Output ofmap_transcripts().
- Returns:
The path the file was written to.
- Return type: