Skip to content

SamRecordClipper still clips past-mate overlap by reference distance, not query distance #760

Description

@nh13

Summary

fgumi-raw-bam::overlap was moved to query-space overlap-clip arithmetic in #759 (fgumi #752, the fgumi side of fgbio#1090). Three sibling implementations in fgumi-sam still carry the superseded reference-space formula:

  • SamRecordClipper::num_bases_extending_past_mate (crates/fgumi-sam/src/clipper.rs)
  • SamRecordClipper::num_bases_extending_past_mate_raw (same file)
  • record_utils::num_bases_extending_past_mate (crates/fgumi-sam/src/record_utils.rs)

All three build a reference coordinate — the mate's soft-only unclipped end — by adding the mate's trailing soft clip, a query distance, to the mate's alignment end, then look up the read position there. With an indel near the read's 3' end that coordinate lands somewhere the read never sequenced: inside a deletion it has no read position at all, read_pos_at_ref_pos(..., false) returns 0, and read_length - 0 clips the entire read. An insertion moves it the other way and under-clips by the insertion's length.

Who is affected

clipper.rs's pair is reached from SamRecordClipper::clip_extending_past_mate_ends, which fgumi clip calls (src/lib/commands/clip.rs). So fgumi clip --clip-overlapping-reads-style usage still mis-clips read-through pairs with a terminal indel.

record_utils::num_bases_extending_past_mate is currently only reached from a #[cfg(test)] bridge in vanilla_caller.rs, so it has no production impact today, but it is public API and will drift further from the raw-byte path.

The consensus callers (simplex, duplex, codec) are not affected — they all route through fgumi-raw-bam::overlap, fixed in #759.

Why it was not fixed in #759

The correct algorithm needs the mate's CIGAR, not two derived reference boundaries. clipper.rs's two functions take mate_unclipped_start / mate_unclipped_end as parameters, so fixing them means changing a public signature and its call sites — a wider change than the consensus fix warranted, and one worth doing on its own so its own before/after clipping counts can be measured.

Suggested fix

Port the algorithm from bases_extending_past_mate in crates/fgumi-raw-bam/src/overlap.rs: take the last reference position both alignments cover (the first, for a negative-strand read), count the query bases each read has past it, and clip the read down to the mate's count.

Two disjoint-alignment cases come with it, and they are not the same case — see the comment below for why this distinction was worth pinning down:

  • The read's 3' end faces the mate but its alignment stops short. Keep the existing extrapolation: project the read's soft-clipped tail one query base per reference base against the mate's soft-only unclipped boundary. Do not answer 0 — read-through is entirely possible here, and answering 0 leaves adapter on the read. The estimate is capped by the read's own soft clipping, so it never takes an aligned base.
  • The read's 3' end faces away from the mate, i.e. the mate lies entirely on the far side. Clip nothing. That is an outward-facing template rather than read-through, and measuring it against the anchor like any other read counts all of the read's query bases as past the mate and takes its aligned ones — the destructive answer overlap clipping reproduces fgbio #1090's reference/query distance conflation #752 exists to remove.

Note that reading a deleted base as the last one before it (htsjdk's returnLastBaseIfDeleted = true) is not a fix — it replaces one reference-space answer with another and still carries the mate's soft clip in the wrong space.

overlap.rs's test suite is a ready-made oracle: the deletion-at-boundary pair, fgbio#1090's own insertion example, an ungapped control that must not move, test_disjoint_alignments_still_clip_their_read_through for the first bullet above, and test_num_bases_extending_past_mate_raw_read_entirely_past_mate for the second.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions