Repository navigation
GTF parser: refuse an annotation that matches nothing, accept GFF3 - #356
Open
BenjaminDEMAILLE wants to merge 2 commits into
Open
BenjaminDEMAILLE wants to merge 2 commits into
BenjaminDEMAILLE wants to merge 2 commits into
Conversation
No exon on a known chromosome is fatal, as in STAR. Attributes split on a space or `=`. An exon without a parent transcript is skipped. Exons are grouped by chromosome and transcript ID, so an ID on two chromosomes no longer produces false junctions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 2, 2026
A value is its first word, as STAR turns `=`, quotes and tabs into spaces: `Parent = rna-1` was read as `= rna-1` and split a transcript in two, and `"T=1"` and `T=2` are both transcript `T` in STAR. An exon on the genome with no parent transcript now gets STAR's invented ID and counts towards "no valid exon lines", so a GTF whose only exons on the genome lack a transcript ID builds as in STAR instead of exiting 1. An exon with no gene ID belongs to MissingGeneID. On a test GTF and GFF3 covering these cases, sjdbList.out.tab, transcriptInfo.tab, exonInfo.tab, geneInfo.tab and exonGeTrInfo.tab are byte-identical to STAR 2.7.11b's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #354 (findings 6 and 7). Independent of the other open PRs apart from
CHANGELOG.mdandDIVERGENCE.md.Problem
extract_junctions_configured(src/junction/gtf.rs) turns GTF exon lines into junctions. Both the genome build (src/index/mod.rs) and mapping-time insertion (src/index/io.rs) call it. It failed in four ways, in both directions.An annotation that matches nothing exits 0. A transcript on an unknown chromosome was skipped with a
log::warn, and zero junctions was treated as "no annotation". An Ensembl GTF (X) on a UCSC FASTA (chrX) gave exit 0, nosjdbList.out.tab, and at mapping, with--quantMode GeneCounts, every read inN_noFeature.--sjdbGTFfile /dev/nulland a GTF with no exon lines did the same.GFF3 could not be read.
parse_attributessplit only on a space, soParent=Twas dropped and--sjdbGTFtagExonParentTranscript Parentstopped withExon missing Parent attribute.One exon with no parent stopped the build.
Exon missing transcript_id attributewas fatal, while the transcriptome builder in the same run skips such an exon (src/quant/transcriptome.rs:245-248), so the two paths disagreed.A transcript ID on two chromosomes invented junctions. Exons were grouped by ID alone, sorted by chromosome-local start, and all placed on the first exon's chromosome.
What STAR does
Run with Homebrew STAR 2.7.11b, with
--sjdbOverhang 50on two 20 kb contigsc1andc2:XexonsFatal INPUT FILE error, no valid exon lines in the GTF file, with a solution line about chromosome namingParent=Tc1 1201 1999transcript_idTonc1(1000-1200, 2000-2200) andc2(1500-1700, 2500-2700)c1 1201 1999,c2 1701 2499, andc1 2201 263643I did not read STAR's source for this PR; these are observed outputs. The file reference in the
DIVERGENCE.mdentry comes from existing comments ingtf.rs.Change
All in
src/junction/gtf.rs:parse_attributessplits on the first space or=, and the value is its first word, as STAR reads it (GTF.cppturns=, quotes and tabs into spaces):Parent = rna-1givesrna-1,"T=1"andT=2both giveT,gene_name "A B"givesA.extract_junctions_configuredcounts every exon on a known chromosome, with or without a parent, as STAR'sexonNdoes. With none it returnsError::Gtf, using STAR's wording. This also covers an empty file and a file with no exon lines. A file whose exons on the genome all lack the parent tag is accepted, as in STAR.log::warnand STAR's invented transcript ID,tr_<chr>_<start>_<end>_<n>(name_orphan_exons, called before both the junction extraction and the transcriptome builder, at genome generation and at mapping). It is a transcript of its own, so it adds no junction but appears intranscriptInfo.tabas in STAR. An exon with no gene ID belongs toMissingGeneID, as in STAR.(seqname, transcript_id).Deliberate divergence:
DIVERGENCE.md§1.9STAR's third junction in the last row above,
c1 2201 263643, runs off the end ofc1. It comes from joining the two chromosomes' exons in one chain. This PR does not reproduce it, and the entry says why. This needs maintainer sign-off, as CONTRIBUTING asks of any divergence. If parity wins, the alternative is to sort by absolute position as STAR does.Not changed
TranscriptomeIndex::from_gtf_exons_configuredstill groups by ID alone and drops a transcript with inconsistent chromosomes (src/quant/transcriptome.rs:289), so such a transcript has its junctions but is not in the transcript tables. I could not compare that with STAR here: Homebrew's macOS binary exits with "could not open input file /geneInfo.tab" on--quantModeruns, as #345 describes.Measured
Same fixtures and arguments, before and after this PR:
mainXexons/dev/nullParent=Tc1 1201 1999, as STARtranscript_idc1 1201 1999, as STARTon two chromosomesc1 1201-1499,1701-1999,2201-2499c1 1201 1999,c2 1701 2499A GTF with no ID shared across chromosomes and all exons parented produces the same junctions: the grouping only splits an ID that spans chromosomes. I did not run
test/parity_suite.sh.A GTF that used to run with no matching exon now stops at startup. That is the point, but it is a behaviour change for anyone relying on it.
Tests
In
src/junction/gtf.rs:same_transcript_id_on_two_chromosomes_is_two_chains,no_exon_on_a_known_chromosome_is_fatal,exon_without_parent_is_its_own_transcript_and_gff3_attributes_parse,only_parentless_exons_on_the_genome_is_not_fatal,attribute_value_is_its_first_word_as_in_star.test_extract_junctions_unknown_chromosomegains one exon on a known chromosome, since a GTF with none is now refused.cargo test --release,cargo clippy --all-targetsandcargo fmt --checkare clean on macOS.Second commit, after review
Review against STAR 2.7.11b found three cases where the first commit was not what STAR does: a file whose only exons on the genome had no transcript ID was refused (STAR exits 0),
Parent = rna-Xsplit one transcript in two, and IDs containing=were kept whole.b1c16e8reads them as STAR does. On a test GTF (IDs"T=1",T=2, exons with notranscript_id, an exon on an unknown chromosome) and a test GFF3 (Parent = rna-1, an exon with onlyID=, no gene IDs), against a Linux build of STAR 2.7.11b:sjdbList.out.tab,transcriptInfo.tab,exonInfo.tab,geneInfo.tab,exonGeTrInfo.tabReadsPerGene.out.tab, 60 readsThe GFF3 difference is older than this PR:
MissingGeneIDholds exons on both strands, and rustar-aligner gives a gene one strand for stranded counting where STAR uses each exon's. Any gene ID spanning both strands is affected the same way.🤖 Generated with Claude Code