Skip to content

fix(bam): per-reference CRAM fetch keeps only that reference's records - #178

Draft
NicoDeVeaux wants to merge 2 commits into
ndv/fix-pr118-unmapped-sweepfrom
ndv/fix-pr118-cram-multiref-fetch
Draft

NicoDeVeaux wants to merge 2 commits into
ndv/fix-pr118-unmapped-sweepfrom
ndv/fix-pr118-cram-multiref-fetch

Conversation

@NicoDeVeaux

Copy link
Copy Markdown

fix(bam): per-reference CRAM fetch keeps only that reference's records

For PR #118, stacked on fix/pr118-unmapped-sweep (base #118 head 0b00cf1). Found while testing that branch; not one of the review's findings.

What

IndexedReader::fetch(tid) on a CRAM now keeps only records whose reference sequence ID is tid.

Why

noodles' CRAM query (noodles-cram 0.94.0, io/reader/query.rs) selects containers through the index but then filters records by interval only, not by reference. In a multi-reference slice (htslib can write these when references have few records, such as many small contigs; --output-fmt-option multi_seq_per_slice=1 forces one) a per-reference fetch over the whole-reference interval also returns the other references' records. On the parallel path each such record is counted once per reference fetch.

Input: tests/data/unmapped_sweep/multi_ref_slice.cram (six records, chr1/chr2 and two unplaced, in one slice; samtools 1.22.1), --reference sweep.fa:

build -t 1 in total -t 4 in total
samtools 1.22.1 flagstat 6 + 0
main bea5571 (htslib) 6 + 0 6 + 0
#118 0b00cf1 6 + 0 11 + 0
fix/pr118-unmapped-sweep 6 + 0 10 + 0
this branch 6 + 0 6 + 0

The per-reference CRAMs (single_ref_slices.cram) give 6 everywhere.

How tested

  • cram_unmapped_sweep_returns_only_unplaced_records now also checks that the per-reference fetches plus the sweep return each record once for the multi-reference CRAM (before: r0, r1, r2, r3 each twice).
  • New integration test test_parallel_cram_multi_reference_slice_counts_each_record_once: flagstat at --threads 1 and 4 must equal samtools 1.22.1's (before: 10 + 0 in total at -t 4).
  • cargo test --release --locked --offline: 206 unit, 12 bin, 20 integration, 2 doc tests pass. cargo fmt --check passes. cargo clippy -- -D warnings reports the same three needless_range_loop errors as unmodified main with the local toolchain (clippy 0.1.90); with that lint allowed, --all-targets is clean.

Not changed, and why

  • Records are still decoded and then dropped; filtering earlier, at the slice level, would need changes in noodles.
  • BAM is unaffected: its per-reference path already checks the reference ID.

🤖 Generated with Claude Code

NicoDeVeaux and others added 2 commits October 23, 2026 11:55
… records

In a multi-reference CRAM slice, `fetch(tid)` returns every record in the
slice whose position overlaps the (whole-reference) interval, whatever its
reference: noodles' query filters on the interval only. On the parallel
path each such record is counted once per reference fetch (flagstat
"in total" 10 instead of 6 for the samtools-written fixture at --threads 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
noodles' CRAM `query` filters records on the interval only, so in a
multi-reference slice `fetch(tid)` also returned other references'
records whose positions fall inside the whole-reference interval. Keep
only records whose reference sequence ID is `tid`, as htslib does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant