feat(degnorm): DegNorm-style transcript degradation normalization - #232
Open
BenjaminDEMAILLE wants to merge 6 commits into
Open
feat(degnorm): DegNorm-style transcript degradation normalization#232BenjaminDEMAILLE wants to merge 6 commits into
BenjaminDEMAILLE wants to merge 6 commits into
Conversation
Two-phase design: --quantMode GeneCoverage captures per-gene, per-exonic-base coverage during alignment (reusing the GeneCounts unique-hit path), and --runMode degNorm merges N sample coverage files through a port of DegNorm's rank-one NMF over-approximation to produce DI scores and adjusted counts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eCoverage.out.bin
…orm run mode) --quantMode GeneCoverage accumulates per-gene, per-exonic-base coverage on the same unique-hit path GeneCounts uses and writes GeneCoverage.out.bin (gzip: header, per-gene table, gene ids, flat u32 coverage). PE mate blocks are merged so an overlapping pair contributes one per base. --runMode degNorm merges N such files and runs a port of DegNorm's rank-one NMF over-approximation (nmf.rs), baseline selection (baseline.rs) and outer depth-factor loop (run.rs), writing DegradationIndex.tab, AdjustedCounts.tab, RawCounts.tab, ScaleFactors.tab and Summary.txt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BenjaminDEMAILLE
force-pushed
the
feat/degnorm
branch
from
August 20, 2026 16:25
ca790da to
80ac217
Compare
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.
What
Implements what DegNorm (Xiong et al., Genome Biology 2019) does: correct RNA-seq counts for sample- and gene-specific transcript degradation, producing a Degradation Index (DI) score per gene per sample plus degradation-adjusted counts.
Why it is two phases and not one
DegNorm's estimator is a rank-one over-approximation of a gene's coverage matrix across samples. With one sample the fit is exact and every DI is 0, so a DI cannot be computed inside a single alignment run. What a single run can do, essentially for free, is the expensive half DegNorm normally pays for by writing a sorted BAM, indexing it, and re-reading it with pysam: the per-gene coverage curves themselves.
Phase 1, during alignment —
--quantMode GeneCoverageaccumulates per-gene, per-exonic-base coverage on the same unique-hit pathGeneCountsalready uses (unique alignment, exactly one overlapping gene), and writesGeneCoverage.out.bin(gzip: magicRSDGNCOV, per-gene table with length + raw count, gene id block, flatu32coverage). Paired-end mate blocks are merged first, so an overlapping pair contributes one per base, matching DegNorm's paired-read handling.Phase 2, after alignment —
--runMode degNormloads N coverage files, validates a shared gene model, and runs the port of DegNorm'snmf.py: leading singular triplet by power iteration on thep x pGram matrix, dual-ascent NMF over-approximation, bin-dropping baseline selection, and the outer depth-factor loop. Upstream's quirks are kept deliberately (the+1DI denominator, the 0.1 / 0.2 / 0.9 thresholds, the[0, 0.9]clamp). Genes fan out overrayon; the downsampling offset comes from--runRNGseedand the gene index, so results do not depend on thread count.New flags
Alignment:
--quantMode GeneCoverage,--degNormSampleId.Merge:
--runMode degNormwith--degNormCoverageFiles(>= 2),--degNormIter(5),--degNormNmfIter(100),--degNormDownsampleRate(1),--degNormMinimaxCoverage(0),--degNormSkipBaselineSelection,--degNormBins(20),--degNormMinHighCoverage(50).Output
<prefix>DegNorm.out/:DegradationIndex.tab,AdjustedCounts.tab,RawCounts.tab,ScaleFactors.tab,Summary.txt.Not STAR
Both flags are rustar-aligner extensions with no STAR counterpart, off by default, and recorded in
DIVERGENCE.md§4.3. Alignment output is unchanged whenGeneCoverageis enabled;tests/degnorm.rsasserts the SAM records andReadsPerGene.out.tabare identical with and without it.Files
src/quant/coverage.rs— accumulator +GeneCoverage.out.binwriter/readersrc/degnorm/nmf.rs— rank-one fit, NMF-OA,ratio_svdsrc/degnorm/baseline.rs— baseline selection, per-gene DIsrc/degnorm/run.rs— multi-sample driver + output tablestests/degnorm.rs— end-to-end: two synthetic samples, one with a 3'-truncated gene, DI ordering assertedVerification
cargo test: 586 lib + 28 integration, all passing (13 new unit tests indegnorm/, 8 inquant::coverage, 5 new params tests, 2 new integration tests).cargo clippy --all-targets: 0 warnings.cargo fmt --check: clean.Docs
User guide at
docs/src/content/docs/guides/degnorm.md(sidebar entry added), design spec atdocs/superpowers/specs/2026-08-20-degnorm-design.md, plusDIVERGENCE.md,CHANGELOG.md,ROADMAP.md(Phase 18),README.md,CLAUDE.md.Known limitations
At least two samples required; coverage must come from phase 1 (third-party BAMs are not read); no coverage-curve plots, no MPI, no warm-start directory. Validation against the Python DegNorm on a real multi-sample dataset is tracked as Phase 18.3.
🤖 Generated with Claude Code