Skip to content

Pass --skip-dup-check to RustQC when --with_umi is set - #1882

Draft
FelixKrueger wants to merge 1 commit into
nf-core:devfrom
FelixKrueger:felix/rustqc-umi-skip-dup-check
Draft

Pass --skip-dup-check to RustQC when --with_umi is set#1882
FelixKrueger wants to merge 1 commit into
nf-core:devfrom
FelixKrueger:felix/rustqc-umi-skip-dup-check

Conversation

@FelixKrueger

Copy link
Copy Markdown
Contributor

Pass --skip-dup-check to RustQC when --with_umi is set

Closes #1881.

Why

RustQC reads the duplicate flag (0x400); it does not mark duplicates itself. In the UMI path, UMI-based deduplication (UMICollapse / UMI-tools) physically removes PCR duplicates, and the workflow skips Picard MarkDuplicates whenever --with_umi is set (workflows/rnaseq/main.nf):

if (!params.skip_markduplicates && !params.with_umi && !markdups_done) {
    BAM_MARKDUPLICATES_PICARD ( ch_genome_bam, ch_fasta_fai )
}

So --use_rustqc --with_umi hands RustQC a BAM with zero flagged reads, and RustQC aborts:

✗ error: ... No duplicate-flagged reads found among N mapped reads ...
RustQC requires that BAM files have duplicates marked (SAM flag 0x400) but NOT removed.

PR #1869 added a validation error for --use_rustqc + --skip_markduplicates, but the UMI path is not covered by that check (skip_markduplicates stays false), so it still fails at the RUSTQC step.

What

Add --skip-dup-check to RustQC's args only when params.with_umi is set (conf/modules/rustqc.config). On a UMI-deduplicated BAM, duplication-based QC is not applicable — the duplicates the metric measures were intentionally removed upstream — so bypassing the check produces the correct (degenerate) result while all other RustQC metrics (RSeQC, Preseq, Qualimap, featureCounts biotype, SAMtools) are computed normally.

Why not also --skip_markduplicates?

Deliberately scoped to with_umi only. The --use_rustqc + --skip_markduplicates case is a genuinely different situation and is already blocked in validateInputParameters() (#1869): there the duplicates are physically present but unmarked, so bypassing the check would make RustQC report incorrect (near-zero) duplication metrics. In the UMI case the duplicates are gone, so a zeroed/skipped dup metric is correct. This PR leaves the #1869 validation untouched.

Alternative considered

Extending the #1869 validation to also block --use_rustqc --with_umi (erroring out, consistent with the --skip_markduplicates decision). That is simpler and lower-risk, but it removes RustQC support for UMI runs entirely, forcing them onto the slower legacy QC path. This PR takes the functionality-preserving route instead; happy to switch to the block-and-error approach if maintainers prefer.

Changes

  • conf/modules/rustqc.config — append params.with_umi ? '--skip-dup-check' : '' to the RUSTQC ext.args list.
  • tests/rustqc.nf.test — add a use_rustqc --with_umi pipeline test (mirrors the UMI params from tests/umi.nf.test).
  • CHANGELOG.md — changelog entry.

Testing notes / to confirm in CI

  • The new test's snapshot still needs to be generated: nf-test test tests/rustqc.nf.test --profile docker --update-snapshot (and tests/rustqc.nf.test.snap committed).
  • Key thing to verify: RustQC's dupradar output is declared non-optional in the module (emit: dupradar path("${prefix}/dupradar/*")). This PR assumes --skip-dup-check bypasses only the preflight check and RustQC still emits dupradar files (degenerate on a deduplicated BAM). If instead it emits nothing under --skip-dup-check, the dupradar output would need optional: true — the new test will surface this.
  • The #XXXX PR-number placeholder in the CHANGELOG line should be replaced once the PR is opened.

UMI deduplication (UMICollapse / UMI-tools) physically removes PCR
duplicates, so the BAM has no reads flagged 0x400. RustQC's
duplicate-marking preflight check then aborts --use_rustqc --with_umi
runs. Duplication QC is not applicable to a deduplicated BAM, so pass
--skip-dup-check in the UMI path. The --use_rustqc + --skip_markduplicates
combination remains blocked in validateInputParameters() (there
duplicates are present-but-unmarked, so bypassing would be incorrect).

Closes nf-core#1881
@FelixKrueger
FelixKrueger force-pushed the felix/rustqc-umi-skip-dup-check branch from 098e1da to a42220f Compare July 6, 2026 10:03
@FelixKrueger

Copy link
Copy Markdown
Contributor Author

Quick update, plus a direction question before I finalise this.

I've confirmed on a real --with_umi --use_rustqc dataset that applying --skip-dup-check in the UMI path (as this PR does) lets RustQC complete instead of aborting on the "No duplicate-flagged reads found" check.

Before I invest in generating the nf-test snapshot for the new use_rustqc --with_umi test, I wanted to check which direction you'd prefer, since #1881 raised two options:

  • This PR (Option B) — pass --skip-dup-check only when --with_umi is set. Rationale: UMI dedup physically removes duplicates, so duplication QC is meaningless by design and a skipped/zeroed dup metric is the correct outcome. This is deliberately different from --skip_markduplicates, where duplicates are present-but-unmarked and bypassing the check would produce incorrect duplication metrics — which is why I left the fix(validation): error when --use_rustqc and --skip_markduplicates are both set #1869 validation untouched.
  • Option A — extend the fix(validation): error when --use_rustqc and --skip_markduplicates are both set #1869 validation to also error on --use_rustqc --with_umi, consistent with how --skip_markduplicates is handled. Simpler and lower-risk, but it removes RustQC support for UMI runs entirely (users fall back to the slower legacy QC path).

Happy to finish either — just let me know which you'd prefer. If Option B, I'll generate and commit the snapshot and mark this ready for review.

One thing the snapshot run will formally confirm: RustQC's dupradar output is declared non-optional in the module, so Option B relies on --skip-dup-check still emitting those files. That held in my local run; if it ever doesn't, the dupradar emit would need optional: true.

@pinin4fjords

Copy link
Copy Markdown
Member

Go with Option B, and I don't think it needs any RustQC or module changes. I tested it.

Ran RustQC 0.2.1 directly on an unmarked BAM (same zero-0x400 state as a UMI-dedup'd BAM). Without --skip-dup-check it aborts exactly as in #1881; with the flag it completes and dupradar/ is populated (dupMatrix + histograms), so the non-optional dupradar emit is fine as-is. No optional: true needed.

More importantly, this just restores parity with the legacy path. The dupradar.r template calls analyzeDuprates, which reads the 0x400 flag and doesn't self-mark either. On a UMI-dedup'd BAM it sees zero flagged reads and reports ~zero duplication, and the umi.nf.test snapshot confirms the legacy path already emits a full (flat) dupRadar set for UMI runs. So RustQC + --skip-dup-check reproduces exactly what the pipeline has always shown for UMI, it's not a degenerate special case we're inventing. The only difference is RustQC has a precondition guard the R path never had, and Option B lifts it just for UMI.

One thing your snapshot run will show: on small/degenerate data RustQC's model fitter can skip a few outputs (intercept_slope, duprateExpDens, the *_mqc files) that R's dupRadar always writes, so the MultiQC dupRadar section for a UMI sample may be thinner. That's fine, just snapshot it as-is.

Happy for you to finish B: generate the snapshot, swap the #XXXX placeholder for 1882, and it'll need a rebase/merge for the dev conflict.

biotype_attr ? "--biotype-attribute ${biotype_attr}" : '',
'--preseq-seed 1 --preseq-seg-len 100000000',
'--tin-seed 1 --junction-saturation-seed 1',
// UMI dedup (UMICollapse / UMI-tools) physically removes PCR duplicates, so the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trim AI narrative as well please

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.

2 participants