Skip to content

RSeQC

RustQC reimplements eight tools from the RSeQC package (including TIN). Each tool produces output files that match the format and content of the original Python implementation, plus native PNG and SVG plots generated directly — no R scripts required.

All RSeQC tools run automatically as part of the rustqc rna command and use the input filename stem as a prefix for output files. Output files are organized into per-tool subdirectories under rseqc/. For example, rustqc rna sample.bam --gtf genes.gtf -o results/ produces RSeQC output files like results/rseqc/bam_stat/sample.bam_stat.txt.

Use --flat-output to write all files directly to the output directory instead of the nested rseqc/<tool>/ subdirectories.

Basic alignment statistics from a single-pass BAM scan.

FileDescription
{stem}.bam_stat.txtFormatted text report with total records, QC failures, duplicates, mapping quality distribution, splice reads, proper pairs, and more

The output format matches bam_stat.py exactly, including the same section headings and number formatting. Key metrics include:

  • Total records, QC-failed, duplicates, non-primary, unmapped
  • Unique and multi-mapped read counts at the configured MAPQ cutoff
  • Read 1 / Read 2 counts, forward/reverse strand counts
  • Splice / non-splice read counts
  • Proper pair and paired-on-different-chromosome counts
  • MAPQ distribution histogram

All values are identical between RSeQC and RustQC on both datasets.

Small dataset
MetricRSeQCRustQC
Total records52,83952,839
QC failed00
Duplicates6,0976,097
Non-primary3,2663,266
Unmapped00
mapq < cutoff1,7421,742
Unique reads41,73441,734
Read 1 / Read 220,871 / 20,86320,871 / 20,863
Sense / Antisense20,869 / 20,86520,869 / 20,865
Non-splice / Splice22,214 / 19,52022,214 / 19,520
Properly paired41,72241,722
Large dataset
MetricRSeQCRustQC
Total records201,605,452201,605,452
QC failed00
Duplicates132,703,364132,703,364
Non-primary15,316,16615,316,166
Unmapped12,490,97712,490,977
mapq < cutoff2,036,9752,036,975
Unique reads39,057,97039,057,970
Read 1 / Read 219,525,894 / 19,532,07619,525,894 / 19,532,076
Sense / Antisense19,529,185 / 19,528,78519,529,185 / 19,528,785
Non-splice / Splice27,689,719 / 11,368,25127,689,719 / 11,368,251
Properly paired39,030,55239,030,552

Library strandedness inference by sampling reads overlapping gene models.

FileDescription
{stem}.infer_experiment.txtStrandedness fractions: failed-to-determine, and the two strand protocols

The output reports the fraction of reads consistent with each strand protocol:

  • Fraction failed to determine — reads that could not be assigned to either protocol
  • Fraction “1++,1—,2+-,2-+” (forward stranded) — reads consistent with the same-strand protocol
  • Fraction “1+-,1-+,2++,2—” (reverse stranded) — reads consistent with the opposite-strand protocol

For paired-end data, the labels are PairEnd with 1++,1--,2+-,2-+ and 1+-,1-+,2++,2--. For single-end: SingleEnd with ++,-- and +-,-+.

Interpreting results:

  • Both fractions near 50% = unstranded library
  • First fraction near 100% = forward stranded (e.g., Ligation protocol)
  • Second fraction near 100% = reverse stranded (e.g., dUTP protocol, most common)

Strandedness mismatch warning: RustQC automatically compares the infer_experiment results against the --stranded flag you specified. If they disagree, a warning is printed at the end of the run suggesting the correct value:

RustQC strandedness mismatch warning
Small dataset - Identical
MetricRSeQCRustQC
Failed to determine0.07750.0775
Fraction sense (1++,1—,2+-,2-+)0.00510.0051
Fraction antisense (1+-,1-+,2++,2—)0.91740.9174
Large dataset - Minor difference (sampling)
MetricRSeQCRustQC
Failed to determine0.06700.1052
Fraction sense (1++,1—,2+-,2-+)0.01170.0185
Fraction antisense (1+-,1-+,2++,2—)0.92130.8763

The difference is caused by a read-sampling mismatch: the upstream infer_experiment.py defaults to sampling only 200,000 reads. On a 186M-read BAM, this early-file subsample draws predominantly from the first few chromosomes, which underrepresents loci where transcripts on both strands overlap, causing the “failed-to-determine” fraction to appear lower than it truly is. RustQC processes all reads, giving a more representative estimate.

Both tools correctly identify the library as strongly reverse-stranded (antisense ~88-92%), so the practical interpretation is identical.

Position-based and sequence-based duplication rate histograms.

FileDescription
{stem}.pos.DupRate.xlsPosition-based duplication histogram (TSV: Occurrence, UniqReadNumber, ReadNumber)
{stem}.seq.DupRate.xlsSequence-based duplication histogram (TSV: Occurrence, UniqReadNumber, ReadNumber)
{stem}.DupRate_plot.rR script for generating duplication rate plots (matching RSeQC format)
{stem}.DupRate_plot.pngDuplication rate plot (PNG)
{stem}.DupRate_plot.svgDuplication rate plot (SVG)

Each TSV file is a tab-separated table where each row represents a duplication level (number of times a read was seen). The columns are:

  • Occurrence — the duplication count (1 = unique, 2 = seen twice, etc.)
  • UniqReadNumber — number of unique read groups at this duplication level
  • ReadNumber — total reads consumed (Occurrence x UniqReadNumber)

Position-based deduplication groups reads by alignment position (chromosome, start, CIGAR-derived exon blocks). Sequence-based deduplication groups reads by the actual read sequence.

The duplication rate plot shows two curves: mapping-based (blue, x markers) and sequence-based (red, dot markers) duplication. The x-axis shows the occurrence count (capped at 500) and the y-axis shows the number of reads at each duplication level. Most reads should appear at low occurrence counts; a long tail indicates high duplication.

RSeQC
RustQC
RSeQC read duplication plot
RustQC read duplication plot

All values are identical on both datasets.

Small dataset (first few duplication levels)
LevelSeq-based (unique reads)Pos-based (unique reads)
1x37,94333,183
2x1,8442,984
3x492693
4x200289
Large dataset (first few duplication levels)
LevelSeq-based (unique reads)Pos-based (unique reads)
1x24,661,23215,958,022
2x5,199,1464,781,421
3x3,211,7132,988,951

Classification of reads across genomic feature types.

FileDescription
{stem}.read_distribution.txtTabular report with total reads, total tags, and per-region breakdown

The output includes:

  • Total Reads and Total Tags (CIGAR M-block midpoints)
  • A table of genomic regions with columns: Group, Total_bases, Tag_count, Tags/Kb
  • Regions: CDS_Exons, 5’UTR_Exons, 3’UTR_Exons, Introns, TSS_up_1kb, TSS_up_5kb, TSS_up_10kb, TES_down_1kb, TES_down_5kb, TES_down_10kb
  • Tags assigned to each region (with priority: CDS > UTR > Intron > Intergenic)

All values are identical on both datasets.

Small dataset
FeatureTotal tagsTags/Kb
CDS Exons56,186-
5’ UTR Exons1,226-
3’ UTR Exons7,495-
Introns2,905-
Other intergenic526-

Total reads: 43,476 | Total tags: 68,660 | Total assigned: 68,134

Large dataset
FeatureTotal tagsTags/Kb
CDS Exons40,028,987401.24
5’ UTR Exons572,203104.93
3’ UTR Exons5,549,140206.28
Introns7,332,5164.87

Total reads: 41,094,945 | Total tags: 55,001,331 | Total assigned: 54,113,746

Splice junction classification against a reference gene model.

FileDescription
{stem}.junction.xlsTSV with all observed junctions: chrom, intron_start(0-based), intron_end(1-based), read_count, annotation_status
{stem}.junction.bedBED12 file with color-coded junctions (red = known, green = partial novel, blue = complete novel)
{stem}.junction_plot.rR script for generating splice event and junction pie charts
{stem}.splice_events.pngSplice events pie chart (PNG)
{stem}.splice_events.svgSplice events pie chart (SVG)
{stem}.splice_junction.pngSplice junctions pie chart (PNG)
{stem}.splice_junction.svgSplice junctions pie chart (SVG)
{stem}.junction_annotation.txtSummary: total/known/partial novel/complete novel event and junction counts

Junctions are classified by comparing splice sites (CIGAR N-operations) against the reference gene model:

  • Known (Annotated) — both donor and acceptor sites match known introns
  • Partial novel — one splice site matches, the other is novel
  • Complete novel — neither splice site matches any known intron

Two pie charts are generated, one for splice events (reads) and one for splice junctions (unique splice sites). Each chart shows the proportion of known (blue), partial novel (red), and complete novel (green) splicing.

RSeQC
RustQC
RSeQC splice events plot
RustQC splice events plot
RSeQC
RustQC
RSeQC splice junction plot
RustQC splice junction plot

All values are identical on both datasets.

Small dataset
MetricRSeQCRustQC
Splice events
Total events23,44823,448
Known22,625 (96.49%)22,625 (96.49%)
Partial novel166 (0.71%)166 (0.71%)
Novel654 (2.79%)654 (2.79%)
Splice junctions
Total junctions3,2613,261
Known2,982 (91.44%)2,982 (91.44%)
Partial novel88 (2.70%)88 (2.70%)
Novel191 (5.86%)191 (5.86%)
Large dataset
MetricRSeQCRustQC
Splice events
Total events12,294,65412,294,654
Known12,155,89412,155,894
Partial novel91,59491,594
Novel41,08241,082
Splice junctions
Total junctions239,792239,792
Known175,159175,159
Partial novel45,55445,554
Novel19,07919,079

The data files and R plotting scripts match upstream RSeQC output.

Splice junction discovery rate at increasing sequencing depths.

FileDescription
{stem}.junctionSaturation_plot.rR script for saturation curve plots
{stem}.junctionSaturation_plot.pngJunction saturation plot (PNG)
{stem}.junctionSaturation_plot.svgJunction saturation plot (SVG)
{stem}.junctionSaturation_summary.txtTSV: percent_of_reads, known_junctions, novel_junctions, all_junctions

The tool subsamples reads at configurable percentages (default: 5%, 10%, …, 100%) and counts how many unique known and novel junctions are detected at each level. This reveals whether sequencing depth is sufficient for full junction detection. A saturated library will show a plateau in the curve; an unsaturated library will show continuing growth.

The junction saturation plot shows three lines: all junctions (blue), known junctions (red), and novel junctions (green). The x-axis is the percentage of total reads used and the y-axis is the number of splice junctions detected (in thousands). A plateau in the curves indicates saturation.

RSeQC
RustQC
RSeQC junction saturation plot
RustQC junction saturation plot

Results are identical between RSeQC and RustQC at full sampling depth on both datasets.

At 100% sampling depth:

Small dataset
MetricRSeQCRustQC
All junctions3,2613,261
Known junctions2,9442,944
Novel junctions317317
Large dataset
MetricRSeQCRustQC
All junctions239,792239,792
Known junctions160,896160,896
Novel junctions78,89678,896

Intermediate sampling percentages show minor stochastic variation from random subsampling, as expected.

Fragment inner distance for paired-end RNA-seq libraries.

FileDescription
{stem}.inner_distance.txtPer-pair detail: readpair_name, inner_distance, classification
{stem}.inner_distance_freq.txtHistogram: lower_bound, upper_bound, count
{stem}.inner_distance_plot.rR script for histogram and density plot
{stem}.inner_distance_plot.pngInner distance histogram with density overlay (PNG)
{stem}.inner_distance_plot.svgInner distance histogram with density overlay (SVG)
{stem}.inner_distance_summary.txtSummary counts by pair classification

The inner distance is defined as the gap between the end of read 1 and the start of read 2 on the mRNA transcript. Negative values indicate read overlap. Pairs are classified as:

  • sameTranscript=Yes, sameExon=Yes — both reads on the same exon
  • sameTranscript=Yes, sameExon=No — reads on the same transcript, different exons (distance calculated on mRNA)
  • sameTranscript=No — reads on different transcripts or no transcript overlap
  • readPairOverlap — reads overlap each other
  • nonExonic — one or both reads not on exons
  • sameChrom=No — reads on different chromosomes
  • unknownChromosome — chromosome not in the gene model

The histogram bins are configurable via --inner-distance-lower-bound, --inner-distance-upper-bound, and --inner-distance-step (defaults: -250 to 250, step 5).

The inner distance plot is a histogram showing the distribution of fragment inner distances, with a Gaussian density curve (red) overlaid. The title displays the mean and standard deviation. The distribution should be approximately normal for a well-prepared library.

RSeQC
RustQC
RSeQC inner distance plot
RustQC inner distance plot

All frequency values are identical on both datasets.

Large dataset summary: mean = 29.378, median = 27.5, std dev = 32.799

The R plotting script (inner_distance_plot.r) matches upstream format exactly.

TIN (Transcript Integrity Number) measures the uniformity of read coverage across a transcript. A TIN score of 100 means perfectly uniform coverage; lower scores indicate degradation or bias. TIN is computed using Shannon entropy of read-start coverage at sampled positions along each transcript’s exonic regions.

RustQC reimplements RSeQC’s tin.py, computing TIN scores automatically as part of rustqc rna in the same single-pass BAM scan as all other analyses.

TIN is particularly useful for:

  • Detecting RNA degradation: degraded samples show low TIN scores across most transcripts
  • Sample QC: median TIN provides a single-number summary of RNA integrity, complementing RIN (RNA Integrity Number) from Bioanalyzer/TapeStation
  • Identifying problematic samples: samples with unusually low median TIN should be flagged for potential exclusion
FileDescription
{stem}.tin.xlsPer-gene TIN scores (TSV: geneID, chrom, tx_start, tx_end, TIN)
{stem}.summary.txtSummary statistics: mean, median, and standard deviation of TIN scores

The {stem}.tin.xls file is a tab-separated file with one row per transcript (gene):

ColumnDescription
geneIDGene identifier from the annotation
chromChromosome
tx_startTranscript start position
tx_endTranscript end position
TINTranscript Integrity Number (0-100)

Transcripts below the minimum coverage threshold are excluded from the output. TIN values are formatted to 2 decimal places.

The {stem}.summary.txt file is a single-row tab-separated summary:

ColumnDescription
Bam_filePath to the input BAM file
TIN(mean)Mean TIN across all transcripts
TIN(median)Median TIN
TIN(stdev)Standard deviation of TIN
Median TINInterpretation
> 70Good RNA integrity
50-70Moderate degradation
< 50Significant degradation — consider excluding
< 30Severe degradation

A bimodal distribution of per-transcript TIN scores (some very high, some very low) can indicate selective degradation of certain transcript classes rather than global degradation.

TIN parameters (sample_size, min_coverage) can be set in the YAML config file. See the Configuration page for details. TIN uses the shared --mapq / -q flag for mapping quality filtering.

The upstream tin.py tool processes each transcript independently and scales poorly with BAM size. On large production datasets (e.g., ~186M reads), tin.py can take over 9 hours and may fail to complete within typical pipeline time limits. RustQC computes TIN scores as part of its single-pass BAM scan, completing all analyses combined in under 15 minutes.

RustQC’s TIN output uses the same file format and column names as RSeQC’s tin.py. TIN scores are computed using the same Shannon entropy formula.

Small dataset - Identical
MetricRSeQCRustQC
TIN mean52.751452.7514
TIN median55.583055.5830
TIN std dev22.533322.5333
Large dataset - Both tools completed

The upstream tin.py took 9h 45m to complete on the large dataset. RustQC completes TIN analysis as part of its single-pass BAM processing in under 15 minutes total (inclusive of all other analyses).

MetricRSeQCRustQC
TIN mean70.19370.208
TIN median77.98078.007
TIN std dev23.58823.597

Summary-level TIN statistics are near-identical. Per-transcript TIN values show larger differences due to the coverage counting approach described above.

RustQC produces gene-level TIN scores (one row per gene, using the longest transcript as representative), while RSeQC’s tin.py produces transcript-level scores. Both formats are compatible with MultiQC.

All output files are designed to be drop-in replacements for the corresponding RSeQC Python tool output. File formats, column names, and numeric precision match the Python originals to facilitate migration from RSeQC to RustQC without downstream pipeline changes.

RustQC also generates the R scripts that RSeQC produces (for junction_annotation, junction_saturation, inner_distance, and read_duplication), maintaining full compatibility. However, unlike RSeQC, RustQC generates the plots directly as PNG and SVG files — there is no need to run the R scripts separately.