Skip to content

genome: --genomeType SuperTranscriptome - #161

Open
BenjaminDEMAILLE wants to merge 3 commits into
scverse:mainfrom
BenjaminDEMAILLE:bd/genome-supertranscriptome
Open

genome: --genomeType SuperTranscriptome#161
BenjaminDEMAILLE wants to merge 3 commits into
scverse:mainfrom
BenjaminDEMAILLE:bd/genome-supertranscriptome

Conversation

@BenjaminDEMAILLE

@BenjaminDEMAILLE BenjaminDEMAILLE commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

--genomeType SuperTranscriptome: condense the genome to the union of its annotated exons.

What changed

Overlapping exons are merged, the merged intervals concatenated, and the transcripts that overlap grouped into superTranscripts — one per condensed chromosome (st0, st1, ...). The index then covers only exonic sequence, and introns cannot be crossed because they are not there. Requires --sjdbGTFfile; --genomeType Transcriptome is still refused.

Four files are written alongside the index, as STAR writes them: superTranscriptSequences.fasta, transcriptSequences.fasta, superTranscriptSJcollapsed.tsv, and fullGenome/conversionToFullGenome.tsv.

The design decision that kept it small

condense returns the annotation already rewritten into condensed coordinates — seqname is the superTranscript, positions are 1-based within it, strand is +. The transcriptome tables, the junction extractor and the suffix-array builder then consume it unchanged; nothing downstream knows the genome was condensed. Genome::from_chromosomes is split out of from_fasta for the same reason: these chromosomes are built rather than read.

Divergence

DIVERGENCE.md §1.2, needing sign-off. STAR condenses the sequence before it fills the reverse-complement half of its genome buffer, so every minus-strand exon reads the allocation's spacer bytes and minus-strand superTranscripts come out as uninitialised memory. Here that half is already filled, so a minus-strand exon yields its actual reverse complement.

This one is not a corner case: it affects any annotation with minus-strand transcripts. A superTranscript built here holds real sequence where STAR's holds spacer bytes, so for those transcripts the two indices differ and alignments against them are not comparable. Reproducing STAR would mean writing code whose correct behaviour is to emit garbage, and a test asserting it.

One behaviour that looks like a gap and is not

A transcript's own intron leaves no junction behind. With nothing else annotated between two exons they end up contiguous in the condensed genome, so there is no gap left to record. A junction survives only when another transcript's exon separates them. Both cases have a test.

Verification

End to end (test_genome_type_supertranscriptome_condenses_to_exons): the 20 kb test genome with its two annotated 50 bp exons condenses to a single 100 bp st0; a read straddling the 200 bp intron aligns 50M at position 26; conversionToFullGenome.tsv names the two source intervals at 10000 and 10250.

Unit tests in src/genome/supertranscript.rs:

  • exons_are_concatenated_and_the_intron_disappears
  • the_annotation_comes_back_in_condensed_coordinates
  • overlapping_exons_are_merged_once
  • a_junction_is_recorded_when_another_exon_separates_the_two
  • disjoint_transcripts_become_separate_supertranscripts
  • minus_strand_supertranscript_is_reverse_complement_not_spacer — the divergence
  • the_conversion_map_has_one_block_per_merged_interval
  • a_gtf_with_no_usable_exon_is_an_error

Gate: 569 lib + 22 integration tests, cargo clippy --all-targets -- -D warnings, cargo fmt --check, MSRV 1.89 — all green.

Output-neutral for --genomeType Full, which is the default and every existing run.

Output neutrality, measured

10k yeast SE reads (CONTRIBUTING's dataset) through this branch and through origin/main, both at default flags: the SAM records are byte-identical (9724 each; the two files differ only in the @PG CL: line, which records the binary path).

Split out of #109 following the one-theme rule in CONTRIBUTING.md; #160 has the transform back-output and #109 keeps the libsais builder.

BenjaminDEMAILLE and others added 3 commits July 29, 2026 11:38
…x, sjdbInsertSave

Four genome-side STAR parameters, each honest about what it does.

`--genomeType` accepts `Full` and refuses `Transcriptome` and
`SuperTranscriptome`. Those need a different index layout entirely; accepting
them would build an ordinary genome under another name.

`--genomeTransformOutput` accepts `None` and refuses `SAM` and `SJ`. Mapping
alignments back to the original coordinate space is not implemented, and a
silent no-op here is worse than a failure: it would report transformed
coordinates as though they were original ones, and nothing downstream could
tell.

`--sjdbInsertSave` accepts `Basic` and `None`. The inserted-junction files are
not retained either way.

`--genomeSuffixLengthMax` is accepted and inert, with the reason recorded on the
flag: the suffix-array builder exposes no comparison bound, and STAR uses this
only to cap work on highly repetitive genomes. The index is identical either
way, so ignoring it changes no output byte — which is the standard this
codebase already applies to the other inert knobs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Condenses the genome to the union of its annotated exons, STAR's
`GTF_superTranscript.cpp`. Overlapping exons merge, the merged intervals
concatenate, overlapping transcripts group into superTranscripts, one per
condensed chromosome. The index then covers only exonic sequence and introns
cannot be crossed, because they are not there.

The condensed genome comes back with its annotation already addressed to it —
`st0`, one-based, plus strand — so the transcriptome tables, the junction
extractor and the suffix array consume it unchanged. Nothing downstream needs to
know the genome was condensed. `Genome::from_chromosomes` is split out of
`from_fasta` for the same reason: these chromosomes are built, not read.

Four files are written alongside the index, as STAR writes them: the
superTranscript and transcript sequences, the collapsed junction table, and
`fullGenome/conversionToFullGenome.tsv`, which is what makes condensed
coordinates translatable back.

D20, deliberately not reproduced. STAR condenses the sequence before it fills
the reverse-complement half of its genome buffer, so every minus-strand exon
reads the allocation's spacer bytes and minus-strand superTranscripts come out
as uninitialised memory. Here that half is already filled, so a minus-strand
exon yields its actual reverse complement. Reproducing STAR would mean writing
code whose correct behaviour is to emit garbage, and a test that asserts it.
The test asserts the hand-derived reverse complement instead, and that no 0x00
byte reaches the output.

One behaviour worth stating because it looks like a missing feature: a
transcript's own intron leaves no junction behind. With nothing else annotated
between two exons, they end up contiguous in the condensed genome, and there is
no gap left to record. A junction survives only when another transcript's exon
separates them. Both cases have a test.

Verified end to end: the 20 kb test genome with its two annotated 50 bp exons
condenses to a single 100 bp `st0`; a read straddling the 200 bp intron aligns
`50M` at position 26, and the conversion map names the two source intervals at
10000 and 10250.
CONTRIBUTING.md asks for one theme per PR and for the description to match the
diff; the transform-output flag, its test and its changelog entry moved to their
own PR, and the D20 divergence moved into the root DIVERGENCE.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant