Skip to content

Document CRAM input and test CRAM/BAM coverage equivalence - #1180

Merged
etal merged 2 commits into
masterfrom
etal/docs/cram-input
Oct 13, 2026
Merged

etal merged 2 commits into
masterfrom
etal/docs/cram-input

Conversation

@etal

@etal etal commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Documents CRAM input, which the code has supported since 6ceef3b but the user documentation never mentioned, and answers the BAM/CRAM/BQSR questions in #1105 with tests behind each claim.

Fixes #1105

Documentation

A new "CRAM input" subsection under coverage in doc/pipeline.rst covers:

  • -f/--fasta is needed to read CRAM. It is accepted by batch (including together with -r/--reference), coverage and autobin, and is forwarded to every pysam and samtools bedcov call. CNVkit builds a missing .crai itself; CRAM indexing does not require the reference.
  • What happens without it. HTSlib searches the -f FASTA (by name), then REF_CACHE and REF_PATH (by the @SQ M5 checksum), then the local file in the @SQ UR tag (by name). When REF_PATH is unset, HTSlib 1.21 and earlier (pysam 0.23 and earlier) fall back to the EBI reference server and download every sequence into ~/.cache/hts-ref. This is the multi-hour slowdown reported in CNVkit coverage takes too long on CRAM file #759. HTSlib 1.22 and later (pysam 0.24 and later) removed that default and stop with Unable to fetch reference / Failure to decode slice. When REF_PATH is set but lacks the sequence, every version fails the same way. The [W::cram_populate_ref] Creating reference cache directory warning appears only when HTSlib creates the default cache directory, so its absence does not rule out downloads.
  • Local reference caches. seq_cache_populate.pl prints the template to assign to REF_CACHE. The documentation recommends assigning the same template to REF_PATH, because HTSlib 1.21 selects the EBI default whenever REF_PATH is empty, regardless of REF_CACHE.
  • Mismatched FASTA. Bases that differ within a decoded slice give MD5 checksum reference mismatch, and CNVkit stops with an error rather than computing coverage from misread alignments. A @SQ length mismatch warning, as in Could not retrieve index file for CRAM files #779, indicates a different assembly. It can appear while decoding still succeeds, when the sequences agree over the region read, so it should be read as a sign of the wrong FASTA.
  • Equivalence. A CRAM and a BAM holding the same alignments yield identical coverage in both the default and --count paths. Duplicate flags are honored in CRAM exactly as in BAM. A new paragraph beside the existing duplicate-marking note states that CNVkit does not read base qualities. GATK ApplyBQSR rewrites only those scores, and apart from GATK's default filter for malformed reads (WellformedReadFilter, the only default on ReadWalker), it passes every read through unchanged. MarkDuplicates output and recalibrated output therefore yield the same coverage. For the same reason, CRAM's lossy quality-score compression has no effect on coverage.

The HTSlib behavior above was checked against the cram_populate_ref source at the 1.21 and 1.22 tags. It was also observed directly with pysam 0.23.3 (HTSlib 1.21) and pysam 0.24.1 (HTSlib 1.24).

Tests

  • test_coverage_skips_marked_duplicates now runs on CRAM as well as BAM, and half of the surviving reads carry Phred 2 at every base. The CRAM is written against a FASTA that is then renamed, so its UR tag names a missing file. Reads run with REF_PATH and REF_CACHE pointed at a nonexistent directory, so decoding depends only on the FASTA passed to CNVkit, and no request ever goes to the network. This isolation is necessary: the synthetic poly-A sequence is served by the EBI MD5 server, and without it, a version of bedcov that dropped the FASTA still passed.
  • test_coverage_cram now requires exact equality, in both the depth and --count paths. It previously used assert_array_almost_equal(..., decimal=1) on the depth path only. The measured difference is 0.0. The fixture has two duplicate-flagged reads inside the chrM target; counting them would raise that target's depth from 82.5 to 84.5. The row count is now asserted absolutely, so an empty result cannot pass. The fixture CRAM's UR tags name an absolute path on the machine that wrote it, so where that path exists, this test cannot show that the FASTA argument is used. The docstring notes this, and points to the test above, which does show it.

Each test was checked against a deliberately broken implementation:

Mutation Result
bedcov counts duplicates for CRAM only test_coverage_cram (by_count=False) and the fmt='cram' subtest fail; BAM subtests pass
bedcov no longer receives the FASTA fmt='cram' subtest fails; BAM subtest passes

Both tests pass under pysam 0.23.3 (the minimum pin) and pysam 0.24.1.

CLI help

The batch, coverage and autobin command summaries and input arguments, and batch -n/--normal, now name CRAM. Previously they named only BAM and bedGraph.

Not changed

  • No numerical output or file format changes. Only documentation, tests, and help text are modified.
  • When a CRAM cannot be decoded, CNVkit currently reports OSError: truncated file, which suggests file corruption rather than a missing or mismatched -f. This is documented here through the HTSlib messages that precede it; a clearer error message is left for a separate change.
  • The autobin positional help has claimed bedGraph input since 83c16bb, but autobin calls ensure_bam_index unconditionally and fails on .bed.gz. This is left for a separate change, because it requires choosing between rejecting bedGraph input and supporting it.

etal added 2 commits October 12, 2026 20:45
Add a "CRAM input" section to the coverage documentation: CRAM files
need the reference FASTA given with -f/--fasta. Without it, and with
REF_PATH unset, HTSlib 1.21 and earlier download every reference
sequence from the EBI server (the multi-hour slowdown in #759), while
later versions stop with a decode error. The section also covers
REF_PATH/REF_CACHE and the errors a mismatched FASTA produces. A note
beside the duplicate-marking guidance states that base quality
recalibration does not change coverage (#1105).

Back those claims with tests: the marked-duplicate test now runs on
CRAM as well as BAM, with reads carrying Phred 2 base qualities and a
CRAM that decodes only through the FASTA passed to CNVkit; the CRAM/BAM
comparison now requires exact equality in both the depth and --count
paths rather than agreement to one decimal place.
These commands read CRAM files when given -f/--fasta, as doc/pipeline.rst
describes, but their command summaries, input arguments, and the batch
-n/--normal option named only BAM and bedGraph input.
@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.39%. Comparing base (b4509b4) to head (89847d9).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1180   +/-   ##
=======================================
  Coverage   76.39%   76.39%           
=======================================
  Files          75       75           
  Lines        8418     8418           
  Branches     1487     1487           
=======================================
  Hits         6431     6431           
  Misses       1556     1556           
  Partials      431      431           
Flag Coverage Δ
unittests 76.39% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@etal
etal merged commit 94bf55a into master Oct 13, 2026
15 checks passed
@etal
etal deleted the etal/docs/cram-input branch October 13, 2026 16:48
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.

CRAM support and potential differences between BAM, MarkDuplicates CRAM, and BQSR-recalibrated CRAM

1 participant