Repository navigation
feat(quant): unspliced targets in TranscriptomeSAM, splicing-status tag and GeneSplicing counts for bulk total RNA-seq - #283
Open
BenjaminDEMAILLE wants to merge 15 commits into
Open
BenjaminDEMAILLE wants to merge 15 commits into
BenjaminDEMAILLE wants to merge 15 commits into
Conversation
…k reads Add src/quant/velocyto.rs, a port of the two steps STARsolo uses for --soloFeatures Velocyto, made usable on bulk reads (one read or read pair plays the role of one UMI): - alignToTranscriptMinOverlap (Transcriptome_classifyAlign.cpp), with the hard-coded minOverlapMinusOne = 6 and the 1 Mb intron cap, run against every transcript that contains the alignment; - the per-UMI collapse of SoloFeature_countVelocyto.cpp into spliced / unspliced / ambiguous per gene, multi-gene reads dropped. Reads are classified once and filtered per strand convention (unstranded, forward, reverse), so one run serves any library type. Includes the counters and the per-gene table / summary writers used by the next commit, and unit tests on a synthetic annotation (retained intron, antisense overlapping genes, boundary exons, single-exon chrM gene, GENCODE _PAR_Y copies, pooled paired-end blocks, indel merging). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…us gene table New opt-in --quantMode value (not in STAR) that runs the Velocyto classifier on every uniquely mapped read or pair and writes: - ReadsPerGeneVelocyto.out.tab: header + one row per gene (geneInfo.tab order) with spliced / unspliced / ambiguous counts for each strand convention (unstranded, forward, reverse), mirroring the three strand columns of ReadsPerGene.out.tab; - ReadsPerGeneVelocyto.summary.tsv: N_unmapped, N_multimapping, N_noFeature, N_multiGene, category totals and the spliced / unspliced / ambiguous fractions per strand convention. Unmapped / multimapping accounting follows GeneCounts. The mode uses the transcriptome tables already loaded for TranscriptomeSAM, so it has the same index requirement. QuantContext now holds optional GeneCounts and GeneVelocyto parts behind count_se_read / count_pe_read / write_outputs; without the new value the counting and ReadsPerGene.out.tab paths are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riptomeSAM New opt-in flag (not in STAR, default Keep = STAR behaviour). With BanRetainedIntron, an unspliced read or pair is not projected onto the transcripts of gene G when one of its aligned blocks (after the usual soft-clip extension) overlaps a retained-intron interval of G. A retained-intron interval is an intron of one isoform that another isoform of the same gene covers with a single exon reaching into both flanking exons. Such reads are equally compatible with the gene's pre-mRNA and with the retaining isoform; STAR projects them onto the retaining isoform only, so in total RNA-seq retained_intron isoforms absorb intronic pre-mRNA reads. Cassette exons and alternative 5'/3' splice sites create no interval, and fragments that cross a junction are kept (processed RNA). The intervals are computed once from the transcriptome tables (RetainedIntrons::build) and attached to the TranscriptomeIndex only when the flag is set; filter_and_project takes the fragment-level junction flag, so a pair is dropped from a gene as soon as either mate overlaps. Counts of dropped alignments/projections are logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/bulk_unspliced.rs builds a synthetic genome with three genes (two on +, one on -), each with a fully spliced isoform and a retained-intron isoform, and simulates a reverse-stranded total RNA library (mature spliced, mature retained-intron and pre-mRNA reads). new_options_leave_existing_outputs_unchanged aligns it three times (GeneCounts + TranscriptomeSAM; the same + GeneVelocyto + --quantTranscriptomePreMRNA Keep; and BanRetainedIntron) and checks that Aligned.out.sam (minus the @PG/@co command lines), ReadsPerGene.out.tab, SJ.out.tab and Log.final.out (minus wall-clock lines) are identical, that the transcriptome BAM records are identical under Keep, that BanRetainedIntron only removes transcriptome records, and that the new files appear only when requested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simulated_total_rna_mixture aligns the simulated library (per gene: 300 mature spliced reads, 0 or 150 mature retained-intron reads, 400 pre-mRNA reads over the gene body) with and without --quantTranscriptomePreMRNA BanRetainedIntron and estimates reads per transcript from Aligned.toTranscriptome.out.bam with a minimal Salmon-like EM (effective-length weighted). - Genes with no retained-intron molecules: the retained-intron isoform share goes from > 0.5 (Keep, i.e. STAR) to 0 (BanRetainedIntron); the summed absolute share error over the three genes must at least halve (it goes from 1.31 to 0.33 on this data). - GeneVelocyto, reverse columns: N_unspliced equals the number of pre-mRNA reads reaching more than 6 bases into the constitutive intron (389/389), mature reads are spliced, forward columns are empty. The per-gene estimates are printed with --nocapture, including the known trade-off: the gene that really expresses its retained-intron isoform loses it under BanRetainedIntron. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New guide docs/.../guides/bulk-total-rna.md: why pre-mRNA inflates retained-intron isoforms in TranscriptomeSAM, the GeneVelocyto classification and output files, the BanRetainedIntron rule and its trade-off (a genuinely expressed retained-intron isoform loses its distinguishing reads). - CLI parameter, output-file and quantification pages point to it. - DIVERGENCE.md 1.4 records both extensions, the STAR code they start from, and the two small departures from classifyAlign (true pair span for the containment test, blocks sorted before the scan). - CHANGELOG Features entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BenjaminDEMAILLE
marked this pull request as ready for review
September 29, 2026 21:06
Velocyto is a single-cell notion; for bulk data the mode is about the splicing status of reads. Rename every user-facing name: - --quantMode GeneVelocyto -> GeneSplicing; - ReadsPerGeneVelocyto.out.tab / .summary.tsv -> ReadsPerGeneSplicing.out.tab / .summary.tsv; - src/quant/velocyto.rs -> src/quant/splice_status.rs, with SpliceStatus / ReadSplicing / SplicingCounts / SplicingQuant types; - docs, CHANGELOG, DIVERGENCE and test names. STARsolo's Velocyto feature is still cited as the origin of the classification, in code comments and STAR source references only. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New opt-in --quantTranscriptomeUnspliced None|Intron|PreMRNA (default None = STAR behaviour, unchanged). With Intron or PreMRNA, one unspliced target per gene is appended to the transcriptome after the annotated transcripts, in gene order, named <gene_id>-I (splici convention): - Intron: union of the introns of all the gene's isoforms (retained introns and cassette-exon introns included), merged, extended by --quantTranscriptomeUnsplicedFlank on each side (default -1 = the index's sjdbOverhang, read length - 1 by STAR convention), clipped to the gene body, merged again, concatenated in genome order; - PreMRNA: the whole gene body. Projection is unchanged: an alignment goes to every target whose blocks contain it, spliced and unspliced alike, so an intronic read compatible with a retained-intron isoform is written to both and the downstream EM decides (NH / MAPQ count all targets). A fragment that crosses a junction only goes to spliced targets. --quantTranscriptomeSAMoutput rules (indels, soft-clip extension, single-end) apply as before. Minus strand targets are read 3' to 5' like transcripts. Also writes Aligned.toTranscriptome.targets.tsv (target_id, gene_id, gene_name, spliced|unspliced status, length, in @sq order: a tx2gene table) and, with --quantTranscriptomeUnsplicedFasta Yes, the unspliced target sequences to Aligned.toTranscriptome.unspliced.fa for Salmon's alignment mode (off by default: genome-sized). The targets are built at alignment time from the transcript tables of any GTF-aware index (no index format change, flank adjustable per run). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
new_options_leave_existing_outputs_unchanged now checks that GeneSplicing and --quantTranscriptomeUnspliced None leave every output identical (including the transcriptome BAM records), that Intron leaves the genomic outputs identical and only appends G1-I, G2-I, G3-I after the annotated @sq lines, and that the targets table / FASTA appear only when asked. simulated_total_rna_mixture compares STAR's projection with Intron and PreMRNA targets on the simulated total RNA library: - every pre-mRNA read inside the retained intron is written to both the retained-intron isoform and <gene_id>-I; - EM retained-intron share error (summed over genes): STAR 1.308, Intron 0.370, PreMRNA 0.310; - relative error on the spliced isoform: STAR 0.236, Intron 1.049, PreMRNA 0.164. PreMRNA recovers the simulated pre-mRNA reads on -I (within 15%); Intron cannot place exonic pre-mRNA reads anywhere but the spliced isoforms (same limitation as splici), which the test states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Superseded by --quantTranscriptomeUnspliced: instead of dropping unspliced reads that overlap a retained intron (which also removed the only evidence for genuinely expressed retained-intron isoforms and gave the exonic pre-mRNA reads to the spliced isoforms), those reads are now written to both the retained-intron isoform and the gene's <gene_id>-I target, and the downstream EM decides. Removes the flag, QuantTranscriptomePreMRNA, RetainedIntrons, the TranscriptomeIndex::retained_introns field and their tests. filter_and_project keeps its fragment-level junction flag, now used to keep spliced fragments off the unspliced targets. Documentation is updated in the following docs commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New --outSAMattributes value sp (rustar-aligner extension, not in any preset, so default output is unchanged). Each alignment record gets sp:A:S (spliced / mature-compatible), sp:A:U (unspliced, needs pre-mRNA) or sp:A:A (ambiguous), from the same STARsolo-derived classifier as --quantMode GeneSplicing, collapsed over every annotated transcript that contains the alignment on either strand (a read-level status, independent of gene assignment). No tag when no transcript contains the alignment. For pairs both mates carry the fragment status. The tag name is not used by STAR / STARsolo (their list: NH HI AS nM NM MD jM jI XS MC ch vA vG vW rB ha GX GN CB UB CR CY UR UY sM sS sQ sF cN) and lower-case tags are reserved for local use by the SAM spec. Needs a GTF-aware index; uses the annotated transcripts only (never the <gene_id>-I targets). Tests: read_status_ignores_gene_boundaries, sp_tag_on_records, sp_attribute_is_opt_in, and splicing_status_tag_in_genomic_sam (records minus the tag equal the default run; pre-mRNA reads in exon 1 / the constitutive intron / the retained intron get S / U / A). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rustar-aligner accepted any --quantMode word and silently ignored the unknown ones (a typo such as Genecounts produced no counts and no error). STAR exits with "unrecognized option in --quantMode" (Parameters.cpp); do the same, keeping "-" as "none". Allowed values: TranscriptomeSAM, GeneCounts, and the rustar-aligner extension GeneSplicing. Only invalid command lines change behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the default --quantTranscriptomeSAMoutput BanSingleEnd_BanIndels_ExtendSoftclip, rebuild_cigar_without_softclips folded the left clip into the first op and the right clip into the last op in one if / else-if chain, so a CIGAR such as 1S97M2S (a single match op between two clips) lost its right clip: the projected record had a 98M CIGAR for a 100-base read, and BAM encoding failed with "read length-sequence length mismatch", aborting the whole run. Found while benchmarking on public 2x100 human total RNA (ENA PRJEB57727): main aborts on the first such read with --quantMode TranscriptomeSAM. STAR extends both ends (ReadAlign_quantTranscriptome .cpp), which the fix now does. Unit test: softclip_rebuild_folds_both_clips_into_a_single_op. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cing Rewrite the bulk total RNA-seq guide around the new design: the <gene_id>-I targets of --quantTranscriptomeUnspliced (Intron vs PreMRNA, flank, projection rules, targets table, FASTA, Salmon recipe), the sp:A splicing-status tag, and GeneSplicing as a secondary per-gene table. CLI, output-file and quantification pages, CHANGELOG and DIVERGENCE.md 1.4 follow; references to the removed BanRetainedIntron are gone. The CHANGELOG also records the --quantMode validation fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/bench_bulk_unspliced/: fetch_data.sh (GRCh38 primary assembly + GENCODE v50, 8 runs of ENA PRJEB57727, first 4 M pairs split into two 2 M pseudo-replicates), run.sh (STAR projection vs --quantTranscriptome Unspliced Intron / PreMRNA, each quantified with salmon quant -a), analyze.py (retained-intron share, -I share, GeneSplicing unspliced fraction, cross-library Spearman rho and slope, pseudo-replicate isoform-proportion and avgTxLength correlations, time, RSS) and compare_main.sh (outputs identical to a main build). Results of the run behind the PR in results_PRJEB57727.txt. Headline (16 libraries, reverse-stranded, 39% unspliced on average): retained-intron share 0.0537 (STAR projection) / 0.0440 (Intron) / 0.0512 (PreMRNA); slope against the unspliced fraction 0.151 / 0.107 / 0.111, Spearman rho 0.92 in all three; fragments reaching the transcriptome BAM 0.62 -> 0.92 per pair; wall time and RSS within noise (peak RSS 29.2 -> 29.6 GiB). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 tasks
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.
Problem
Ribo-depleted ("total") RNA-seq contains a lot of unspliced pre-mRNA, and
--quantMode TranscriptomeSAMhas nowhere to put it. The projection (Transcriptome_quantAlign.cpp) sends a genomic alignment to every transcript whose exons contain it. An unspliced read inside an intron fits no fully spliced isoform, but GENCODE annotates manyretained_intronisoforms whose single exon covers that intron: the read is projected onto that isoform only and Salmon / RSEM must assign it there. The amount follows the library's pre-mRNA content, not the isoform's abundance. Bulk users also have no STAR measure of spliced vs unspliced reads (GeneFull/Velocytoexist only in STARsolo).Motivating measurements (made by the PR author with Salmon, not with rustar-aligner): 96 whole-blood libraries, ribo-depleted total RNA, reverse-stranded, 2x51 bp. With Salmon and full GRCh38 decoys, about 58% of fragments went to decoys (intronic pre-mRNA, expected in total RNA). A pre-mRNA read compatible with a
retained_intronisoform is assigned to it; library by library, the share of reads onretained_intronisoforms tracks the decoy share (Spearman rho = 0.94, n = 96), unchanged at fixed residual gDNA and unrelated to TIN. Isoform proportions, hence tximportavgTxLengthoffsets, did not reproduce between technical replicates (r ~ 0.05).The public benchmark below reproduces the pattern with rustar-aligner's STAR-faithful projection: over 16 public whole-blood libraries the retained-intron share tracks the unspliced fraction with Spearman rho = 0.92.
Refs COMBINE-lab/salmon#1229. See also https://support.bioconductor.org/p/9163851/, He et al. 2022 (splici, Nat Methods, doi:10.1038/s41592-022-01408-3) and Soneson et al. 2021 (PLoS Comput Biol, doi:10.1371/journal.pcbi.1008585). I know of no published bulk benchmark, so this PR brings its own.
What this PR adds (all opt-in, all rustar-aligner extensions)
1. Unspliced targets in the transcriptome BAM:
--quantTranscriptomeUnspliced None|Intron|PreMRNADefault
None= STAR. WithIntronorPreMRNA, one target per gene,<gene_id>-I(splici naming), is appended after the annotated transcripts,@SQlines in gene order:Intron: union of the introns of all the gene's isoforms (retained introns and cassette-exon introns included), merged, extended by--quantTranscriptomeUnsplicedFlankon each side (default-1= the index'ssjdbOverhang, i.e. read length - 1 by STAR convention), clipped to the gene body, merged again, concatenated in genome order.PreMRNA: the whole gene body.Projection is the unchanged STAR logic applied to all targets: an alignment goes to every target that contains all its blocks, spliced and unspliced alike. A read in a retained intron is written to both the retained-intron isoform and
<gene_id>-I(NH / HI / MAPQ count all targets) and the EM decides; a read in a constitutive intron now reaches<gene_id>-Iinstead of being dropped; a fragment crossing a splice junction only goes to spliced targets.--quantTranscriptomeSAMoutputrules (indels, soft-clip extension, single-end) apply to every target; minus-strand targets are read 3' to 5' like transcripts.Also written:
Aligned.toTranscriptome.targets.tsv(target_id, gene_id, gene_name, spliced|unspliced status, length, in@SQorder: a tx2gene table) and, with--quantTranscriptomeUnsplicedFasta Yes,Aligned.toTranscriptome.unspliced.fa(the-Isequences, needed next to the transcript FASTA bysalmon quant -a; off by default because it is genome-sized and only depends on index + flank).Targets are built at alignment time from the transcript tables of any GTF-aware index: no index format change, flank adjustable per run, works with indexes built before this PR. Cost measured below: none visible in wall time, +0.3 to 0.4 GiB RSS.
2.
sp:Asplicing-status tag in the genomic SAM/BAM:--outSAMattributes ... spIn no preset (default output unchanged).
sp:A:Sspliced (compatible only with mature mRNA),sp:A:Uunspliced (needs pre-mRNA),sp:A:Aambiguous; no tag when no annotated transcript contains the alignment; both mates carry the fragment status. The name is not used by STAR / STARsolo (checked againstparametersDefault); lower-case tags are reserved for local use by the SAM spec.3. Per-gene table:
--quantMode GeneSplicing(secondary, cheap)ReadsPerGeneSplicing.out.tab(spliced / unspliced / ambiguous per gene for the unstranded / forward / reverse conventions ofReadsPerGene.out.tab) andReadsPerGeneSplicing.summary.tsv(read accounting and fractions). It only runs when requested and reuses the classifier of thesptag, so I kept it; its unspliced fraction is the per-library pre-mRNA measure used in the benchmark.The classifier behind 2 and 3 is a port of STARsolo's Velocyto logic (provenance):
alignToTranscriptMinOverlapfromTranscriptome_classifyAlign.cpp(minOverlapMinusOne = 6, 1 Mb intron cap, spliced alignments touching an intron rejected) and the per-UMI collapse ofSoloFeature_countVelocyto.cpp, one read or pair standing for one UMI.Also in this PR
BanSingleEnd_BanIndels_ExtendSoftclip, a CIGAR such as1S97M2Slost its right clip when rebuilt (single-op body), producing a 98M record for a 100-base read; BAM encoding then failed andmainaborts the whole run (read length-sequence length mismatch) on the public data below. STAR extends both ends (ReadAlign_quantTranscriptome.cpp); fixed, with a unit test.--quantModevalues were silently ignored (a typo gave no counts and no error); now rejected as STAR does (Parameters.cpp).Design history and rejected alternatives
This PR went through two earlier designs, kept in the commit history (no force-push):
--quantMode GeneVelocyto): renamed, Velocyto being a single-cell notion (refactor(quant): rename GeneVelocyto to GeneSplicing).--quantTranscriptomePreMRNA BanRetainedIntron: do not project an unspliced read overlapping an intron that another isoform retains): removed. It also deleted the only evidence for genuinely expressed retained-intron isoforms (simulation: true share 0.333 estimated as 0.000) and pushed the exonic pre-mRNA reads onto the spliced isoforms (+45% on them). Giving the EM an unspliced target is the principled fix and is what splici does.GeneSplicingtable, but it does not fix isoform quantification, which needs both kinds of targets in one reference.-Irecord per disjoint intron interval (splici's-I1,-I2...; loses pairs whose mates fall in two introns, so the intervals are concatenated into one target per gene); using GENCODE'stranscript_type(annotation-specific; the rules here use structure only); tagging projections instead of adding targets (Salmon / RSEM ignore tags); building targets atgenomeGenerate(would change the index format and freeze the flank).Compatibility / divergence from STAR
tests/bulk_unspliced.rs::new_options_leave_existing_outputs_unchanged:GeneSplicing+--quantTranscriptomeUnspliced NoneleaveAligned.out.sam(minus@PG/@CO),ReadsPerGene.out.tab,SJ.out.tab,Log.final.out(minus times) and the transcriptome BAM records identical;Intronleaves the genomic outputs identical and only appends@SQlines after the annotated ones.splicing_status_tag_in_genomic_sam: records withspminus the tag equal the default records.mainbuild on public data (scripts/bench_bulk_unspliced/compare_main.sh, 2 libraries x 2 M pairs, GRCh38 + GENCODE v50):Aligned.out.bamrecords and header (minus@PG/@CO), transcriptome BAM records,SJ.out.tabandLog.final.outidentical, for the branch with and withoutGeneSplicing --quantTranscriptomeUnspliced None(4.4 M genomic and 33 to 37 M transcriptome records). This check uses--quantTranscriptomeSAMoutput BanSingleEndbecausemainaborts on this data with the default mode (fix above); with the default mode the branch differs frommainexactly wheremainfails.DIVERGENCE.md1.4, including two small departures of the classifier port fromclassifyAlign(containment test on the true pair span; blocks sorted before the scan). Needs maintainer sign-off per CONTRIBUTING.QuantContextholds optional GeneCounts / GeneSplicing /spparts (4 call sites inlib.rs);filter_and_projecttakes a fragment-level junction flag;TranscriptomeIndexgains an optionalunsplicedmarker;SamAttributesgainsSP(bit 15). Default hot path: unchanged work (new code behindOptions).Tests
src/quant/transcriptome.rs:unspliced_mode_from_str,unspliced_intervals_intron_and_premrna(union of isoform introns incl. retained ones, flanks clipped and merged, gene body),single_exon_gene_has_no_intron_target,unspliced_targets_are_appended_in_gene_order(annotated order preserved),unspliced_target_projection(retained intron -> RI isoform +-I, spliced fragment -> spliced only, constitutive exon untouched, intron/exon boundary needs the flank, cassette exon -> inclusion isoform +-I),unspliced_target_on_minus_strand_is_flipped,targets_table_and_unspliced_fasta,softclip_rebuild_folds_both_clips_into_a_single_op.src/quant/splice_status.rs(19 tests): per-block calls, STAR's collapse rules, full classification on a synthetic GTF (retained intron, antisense gene inside an intron, boundary exons, protruding read, single-exon chrM gene, GENCODE_PAR_Ycopies, pooled pair blocks, indel merging), table / summary writers,read_status_ignores_gene_boundaries,sp_tag_on_records.src/params/mod.rs:quant_gene_splicing_is_opt_in,sp_attribute_is_opt_in,unknown_quant_mode_is_rejected.tests/bulk_unspliced.rs(synthetic genome, 3 genes on both strands, each with a spliced isoform A and a retained-intron isoform R; reverse-stranded library of 300 mature A, 0 / 150 / 0 mature R and 400 pre-mRNA reads per gene):simulated_total_rna_mixture: every pre-mRNA read inside the retained intron is written to both R and<gene>-I. Minimal effective-length EM on the transcriptome BAM:Summed RI-share error 1.308 / 0.370 / 0.310; summed relative error on A 0.236 / 1.049 / 0.164;
PreMRNArecovers the 400 simulated pre-mRNA reads per gene on-Iwithin 15%.Introncannot place the exonic part of pre-mRNA anywhere but the spliced isoforms (splici has the same limitation), which the test states.splicing_status_tag_in_genomic_sam: pre-mRNA reads in exon 1 / the constitutive intron / the retained intron get S / U / A.cargo fmt --check,cargo clippy --all-targets(0 warnings) andcargo testpass locally and in CI.Public benchmark
Scripts and raw results:
scripts/bench_bulk_unspliced/(results_PRJEB57727.txt). GRCh38 primary assembly + GENCODE v50 comprehensive annotation (full genome index, no subset); ENA PRJEB57727 (whole blood in PAXgene, rRNA + globin depleted, reverse-stranded, 2x100 bp), 8 runs, first 4 M pairs of each split into two 2 M-pair pseudo-replicates (16 libraries). Each library aligned three times (STAR projection,Intron,PreMRNA), each transcriptome BAM quantified withsalmon quant -a(salmon 2.8.0). macOS aarch64, 16 threads.IntronPreMRNA-ItargetsGeneSplicing (reverse column, picked from the data): unspliced fraction 0.29 to 0.58 across the 16 libraries (mean 0.39). genomeGenerate (full GRCh38 + GENCODE v50, 644 k transcripts): 926 s, peak RSS 20.7 GiB.
What this says, honestly:
Intron) and its dependence on pre-mRNA content (slope -26% to -29%), and raise the fragments reaching the transcriptome BAM from 0.62 to 0.92 per pair, but they do not remove the rank correlation (rho stays 0.92). With Salmon's length-normalised EM, a short retained-intron isoform still explains local intronic coverage better than a long gene-body target; part of the correlation may also be biological (libraries richer in nascent RNA also carry more genuine retention). This is the same limit splici has.PreMRNA(0.817 -> 0.828). The technical-replicate effect in the motivation (r ~ 0.05) cannot be tested on this dataset; the PR author will run the private benchmark and comment the numbers here.--sjdbOverhang 49(ENA metadata suggested 2x50 bp, the reads are 2x100 bp), which is valid but not STAR's recommended value and sets theIntronflank to 49; wall times are noisy (other jobs on the machine).Limits
Introncounts the exonic part of pre-mRNA as mature and drops pairs with one mate in an exon and one in an intron (unless an isoform retains that intron);PreMRNAavoids both but makes every exonic read also compatible with-I, leaving the split to the EM.-I; an intronic read of gene A inside gene B's body goes to both-Itargets (EM decides).GeneSplicingdrops multi-gene reads (N_multiGene, as STAR's Velocyto does); with GENCODE v50 that is 12% to 25% (mean 18%) of uniquely mapped pairs on the reverse column in the benchmark, which I attribute to overlapping lncRNA / readthrough annotation (not measured). Thesptag ignores gene boundaries and strand.-Itarget uses the chromosome and strand of the gene's first transcript.-IFASTA is genome-sized, hence opt-in.This PR was prepared with Claude Code and reviewed by the user.
🤖 Generated with Claude Code