-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add methods to fix mate info on non-primaries and templates #204
base: main
Are you sure you want to change the base?
Changes from 38 commits
8a4e218
cb734da
3dd8bc4
e4d076d
4ee5878
8bca675
b880b65
56722b0
c74834b
8c69815
386d058
d8b2c42
a53f735
6f3c514
4e64644
8c4ce6f
dc17e0d
935184b
0f5200f
f7e81c4
e328f45
8a4973b
f858479
06938d7
6dea881
0d6ab25
ce520e1
4c7f8b3
8baaa48
206cec5
f95cc9e
7c4a938
7a2ca2c
dd7dba5
841a44a
7140063
4f2fbea
ec56350
7116964
716de79
8d2617c
0649c67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -178,6 +178,7 @@ | |||||||||||||||||||||||||||||||||||||||||
from pysam import AlignedSegment | ||||||||||||||||||||||||||||||||||||||||||
from pysam import AlignmentFile as SamFile | ||||||||||||||||||||||||||||||||||||||||||
from pysam import AlignmentHeader as SamHeader | ||||||||||||||||||||||||||||||||||||||||||
from typing_extensions import Self | ||||||||||||||||||||||||||||||||||||||||||
from typing_extensions import deprecated | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
import fgpyo.io | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -863,40 +864,111 @@ def sum_of_base_qualities(rec: AlignedSegment, min_quality_score: int = 15) -> i | |||||||||||||||||||||||||||||||||||||||||
return score | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
def _set_common_mate_fields(dest: AlignedSegment, source: AlignedSegment) -> None: | ||||||||||||||||||||||||||||||||||||||||||
"""Set common mate info on a destination alignment to its mate's non-supplementary alignment. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||||||
dest: The alignment to set the mate info upon. | ||||||||||||||||||||||||||||||||||||||||||
source: The alignment to use as a mate reference. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Raises: | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If dest and source are of the same read ordinal. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If source is supplementary (and not purely primary or secondary). | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If dest and source do not share the same query name. | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
if source.is_read1 is dest.is_read1: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("source and dest records must be of different read ordinals!") | ||||||||||||||||||||||||||||||||||||||||||
if source.is_supplementary: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("Mate info must be set from a non-supplementary source!") | ||||||||||||||||||||||||||||||||||||||||||
if source.query_name != dest.query_name: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("Cannot set mate info on alignments with different query names!") | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
dest.next_reference_id = source.reference_id | ||||||||||||||||||||||||||||||||||||||||||
dest.next_reference_name = source.reference_name | ||||||||||||||||||||||||||||||||||||||||||
dest.next_reference_start = source.reference_start | ||||||||||||||||||||||||||||||||||||||||||
dest.mate_is_forward = source.is_forward | ||||||||||||||||||||||||||||||||||||||||||
dest.mate_is_mapped = source.is_mapped | ||||||||||||||||||||||||||||||||||||||||||
dest.set_tag("MC", source.cigarstring) | ||||||||||||||||||||||||||||||||||||||||||
dest.set_tag("MQ", source.mapping_quality) | ||||||||||||||||||||||||||||||||||||||||||
dest.set_tag("ms", sum_of_base_qualities(source)) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
def set_mate_info( | ||||||||||||||||||||||||||||||||||||||||||
r1: AlignedSegment, | ||||||||||||||||||||||||||||||||||||||||||
r2: AlignedSegment, | ||||||||||||||||||||||||||||||||||||||||||
rec1: AlignedSegment, | ||||||||||||||||||||||||||||||||||||||||||
rec2: AlignedSegment, | ||||||||||||||||||||||||||||||||||||||||||
clintval marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
is_proper_pair: Callable[[AlignedSegment, AlignedSegment], bool] = is_proper_pair, | ||||||||||||||||||||||||||||||||||||||||||
isize: Callable[[AlignedSegment, AlignedSegment], int] = isize, | ||||||||||||||||||||||||||||||||||||||||||
) -> None: | ||||||||||||||||||||||||||||||||||||||||||
"""Resets mate pair information between reads in a pair. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||||||
r1: Read 1 (first read in the template). | ||||||||||||||||||||||||||||||||||||||||||
r2: Read 2 with the same query name as r1 (second read in the template). | ||||||||||||||||||||||||||||||||||||||||||
rec1: The first record in the pair. | ||||||||||||||||||||||||||||||||||||||||||
rec2: The second record in the pair. | ||||||||||||||||||||||||||||||||||||||||||
is_proper_pair: A function that takes the two alignments and determines proper pair status. | ||||||||||||||||||||||||||||||||||||||||||
isize: A function that takes the two alignments and calculates their isize. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Raises: | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If rec1 and rec2 are of the same read ordinal. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If either rec1 or rec2 is supplementary (and not purely primary or secondary). | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If rec1 and rec2 do not share the same query name. | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
if r1.query_name != r2.query_name: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("Cannot set mate info on alignments with different query names!") | ||||||||||||||||||||||||||||||||||||||||||
for dest, source in [(rec1, rec2), (rec2, rec1)]: | ||||||||||||||||||||||||||||||||||||||||||
_set_common_mate_fields(dest=dest, source=source) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
template_length = isize(rec1, rec2) | ||||||||||||||||||||||||||||||||||||||||||
rec1.template_length = template_length | ||||||||||||||||||||||||||||||||||||||||||
rec2.template_length = -template_length | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
for src, dest in [(r1, r2), (r2, r1)]: | ||||||||||||||||||||||||||||||||||||||||||
dest.next_reference_id = src.reference_id | ||||||||||||||||||||||||||||||||||||||||||
dest.next_reference_name = src.reference_name | ||||||||||||||||||||||||||||||||||||||||||
dest.next_reference_start = src.reference_start | ||||||||||||||||||||||||||||||||||||||||||
dest.mate_is_forward = src.is_forward | ||||||||||||||||||||||||||||||||||||||||||
dest.mate_is_mapped = src.is_mapped | ||||||||||||||||||||||||||||||||||||||||||
dest.set_tag("MC", src.cigarstring) | ||||||||||||||||||||||||||||||||||||||||||
dest.set_tag("MQ", src.mapping_quality) | ||||||||||||||||||||||||||||||||||||||||||
proper_pair = is_proper_pair(rec1, rec2) | ||||||||||||||||||||||||||||||||||||||||||
rec1.is_proper_pair = proper_pair | ||||||||||||||||||||||||||||||||||||||||||
rec2.is_proper_pair = proper_pair | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
r1.set_tag("ms", sum_of_base_qualities(r2)) | ||||||||||||||||||||||||||||||||||||||||||
r2.set_tag("ms", sum_of_base_qualities(r1)) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
template_length = isize(r1, r2) | ||||||||||||||||||||||||||||||||||||||||||
r1.template_length = template_length | ||||||||||||||||||||||||||||||||||||||||||
r2.template_length = -template_length | ||||||||||||||||||||||||||||||||||||||||||
def set_mate_info_on_secondary(secondary: AlignedSegment, mate_primary: AlignedSegment) -> None: | ||||||||||||||||||||||||||||||||||||||||||
"""Set mate info on a secondary alignment to its mate's primary alignment. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
proper_pair = is_proper_pair(r1, r2) | ||||||||||||||||||||||||||||||||||||||||||
r1.is_proper_pair = proper_pair | ||||||||||||||||||||||||||||||||||||||||||
r2.is_proper_pair = proper_pair | ||||||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||||||
secondary: The secondary alignment to set mate information upon. | ||||||||||||||||||||||||||||||||||||||||||
mate_primary: The primary alignment of the secondary's mate. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Raises: | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If secondary and mate_primary are of the same read ordinal. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If secondary and mate_primary do not share the same query name. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If mate_primary is not purely a primary alignment. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If secondary is not marked as a secondary alignment. | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
if mate_primary.is_secondary or mate_primary.is_supplementary: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("The mate primary must not be secondary or supplementary!") | ||||||||||||||||||||||||||||||||||||||||||
if not secondary.is_secondary: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("Cannot set mate info on an alignment not marked as secondary!") | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
_set_common_mate_fields(dest=secondary, source=mate_primary) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
def set_mate_info_on_supplementary(supp: AlignedSegment, mate_primary: AlignedSegment) -> None: | ||||||||||||||||||||||||||||||||||||||||||
"""Set mate info on a supplementary alignment to its mate's primary alignment. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||||||
supp: The supplementary alignment to set mate information upon. | ||||||||||||||||||||||||||||||||||||||||||
mate_primary: The primary alignment of the supplementary's mate. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Raises: | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If supp and mate_primary are of the same read ordinal. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If supp and mate_primary do not share the same query name. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If mate_primary is not purely a primary alignment. | ||||||||||||||||||||||||||||||||||||||||||
ValueError: If supp is not marked as a supplementary alignment. | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
if mate_primary.is_secondary or mate_primary.is_supplementary: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("The mate primary must not be secondary or supplementary!") | ||||||||||||||||||||||||||||||||||||||||||
if not supp.is_supplementary: | ||||||||||||||||||||||||||||||||||||||||||
raise ValueError("Cannot set mate info on an alignment not marked as supplementary!") | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
_set_common_mate_fields(dest=supp, source=mate_primary) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
# NB: for a non-secondary supplemental alignment, set the following to the same as the primary. | ||||||||||||||||||||||||||||||||||||||||||
if not supp.is_secondary: | ||||||||||||||||||||||||||||||||||||||||||
supp.is_proper_pair = mate_primary.is_proper_pair | ||||||||||||||||||||||||||||||||||||||||||
supp.template_length = -mate_primary.template_length | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+965
to
968
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think template-length is correct, since the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I'm having a hard time seeing how this is wrong. Really wish we could whiteboard! Here, "mate primary" is the primary alignment of the supplemental's mate. What we want to do is have the supplement's template length be equal to the primary of the supplement (which we unfortunately do not have access to). However, the implementation in For secondary supplementals (and for secondaries in general) I am not setting template length anywhere because I feel the specification leaves this ambiguous, and a smarter aligner (or custom code) might set specific template lengths on the secondaries and I don't want to override that info. I feel the spec. is ambiguous for secondaries because I don't see reference to |
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
@deprecated("Use `set_mate_info()` instead. Deprecated after fgpyo 0.8.0.") | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -922,7 +994,7 @@ def set_pair_info(r1: AlignedSegment, r2: AlignedSegment, proper_pair: bool = Tr | |||||||||||||||||||||||||||||||||||||||||
r2.is_read2 = True | ||||||||||||||||||||||||||||||||||||||||||
r2.is_read1 = False | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
set_mate_info(r1=r1, r2=r2, is_proper_pair=lambda a, b: proper_pair) | ||||||||||||||||||||||||||||||||||||||||||
set_mate_info(rec1=r1, rec2=r2, is_proper_pair=lambda a, b: proper_pair) | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
@attr.s(frozen=True, auto_attribs=True) | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -1164,6 +1236,31 @@ def all_recs(self) -> Iterator[AlignedSegment]: | |||||||||||||||||||||||||||||||||||||||||
for rec in recs: | ||||||||||||||||||||||||||||||||||||||||||
yield rec | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
def set_mate_info( | ||||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||||
is_proper_pair: Callable[[AlignedSegment, AlignedSegment], bool] = is_proper_pair, | ||||||||||||||||||||||||||||||||||||||||||
isize: Callable[[AlignedSegment, AlignedSegment], int] = isize, | ||||||||||||||||||||||||||||||||||||||||||
) -> Self: | ||||||||||||||||||||||||||||||||||||||||||
"""Reset all mate information on every record in the template. | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||||||
is_proper_pair: A function that takes two alignments and determines proper pair status. | ||||||||||||||||||||||||||||||||||||||||||
isize: A function that takes the two alignments and calculates their isize. | ||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||
if self.r1 is not None and self.r2 is not None: | ||||||||||||||||||||||||||||||||||||||||||
set_mate_info(self.r1, self.r2, is_proper_pair=is_proper_pair, isize=isize) | ||||||||||||||||||||||||||||||||||||||||||
if self.r1 is not None: | ||||||||||||||||||||||||||||||||||||||||||
for rec in self.r2_secondaries: | ||||||||||||||||||||||||||||||||||||||||||
set_mate_info_on_secondary(secondary=rec, mate_primary=self.r1) | ||||||||||||||||||||||||||||||||||||||||||
for rec in self.r2_supplementals: | ||||||||||||||||||||||||||||||||||||||||||
set_mate_info_on_supplementary(supp=rec, mate_primary=self.r1) | ||||||||||||||||||||||||||||||||||||||||||
if self.r2 is not None: | ||||||||||||||||||||||||||||||||||||||||||
for rec in self.r1_secondaries: | ||||||||||||||||||||||||||||||||||||||||||
set_mate_info_on_secondary(secondary=rec, mate_primary=self.r2) | ||||||||||||||||||||||||||||||||||||||||||
for rec in self.r1_supplementals: | ||||||||||||||||||||||||||||||||||||||||||
set_mate_info_on_supplementary(supp=rec, mate_primary=self.r2) | ||||||||||||||||||||||||||||||||||||||||||
return self | ||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+1246
to
+1258
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was expecting this:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With my last commit, I still have two separate functions for secondaries and supplementaries. They aren't that different from
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
def write_to( | ||||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||||
writer: SamFile, | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank-you!