Skip to content

Commit 76282b9

Browse files
committed
separated BND and INS/DEL signal scoring
1 parent 1c048ab commit 76282b9

4 files changed

Lines changed: 145 additions & 53 deletions

File tree

src/svirlpool/candidateregions/signalstrength_to_crs.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,14 +395,19 @@ def process_chromosome_to_proto_crs(args_tuple) -> tuple[str, Path, dict]:
395395
"""Worker function to process one chromosome and create proto-CRs.
396396
397397
Args:
398-
args_tuple: (chr_name, chr_signals_file, chr_repeats_file, tmp_dir, buffer_region_radius)
398+
args_tuple: (chr_name, chr_signals_file, chr_repeats_file, tmp_dir, buffer_region_radius, bnd_region_radius)
399399
400400
Returns:
401401
(chr_name, proto_crs_file, statistics_dict)
402402
"""
403-
chr_name, chr_signals_file, chr_repeats_file, tmp_dir, buffer_region_radius = (
404-
args_tuple
405-
)
403+
(
404+
chr_name,
405+
chr_signals_file,
406+
chr_repeats_file,
407+
tmp_dir,
408+
buffer_region_radius,
409+
bnd_region_radius,
410+
) = args_tuple
406411

407412
logger.info(f"Processing chromosome {chr_name}...")
408413

@@ -432,7 +437,7 @@ def process_chromosome_to_proto_crs(args_tuple) -> tuple[str, Path, dict]:
432437
for s in signals:
433438
sv_type = s[4].sv_type
434439
if sv_type in (3, 4):
435-
margin = 3 * buffer_region_radius
440+
margin = bnd_region_radius
436441
elif sv_type in (1, 2):
437442
margin = max(
438443
50, int(abs(s[4].size) * 0.5)
@@ -775,6 +780,7 @@ def create_candidate_regions(
775780
dropped: Path | None = None,
776781
bedgraph: Path | None = None,
777782
tmp_dir_path: Path | None = None,
783+
bnd_region_radius: int = 300,
778784
) -> None:
779785
csv.field_size_limit(sys.maxsize)
780786

@@ -872,6 +878,7 @@ def create_candidate_regions(
872878
chr_repeat_files.get(chr_name, tmp_dir / f"{chr_name}_repeats.bed"),
873879
tmp_dir,
874880
buffer_region_radius,
881+
bnd_region_radius,
875882
))
876883

877884
chr_results = []
@@ -972,6 +979,7 @@ def run(args, **kwargs):
972979
min_cr_size=args.min_cr_size,
973980
dropped=args.dropped,
974981
tmp_dir_path=getattr(args, "tmp_dir", None),
982+
bnd_region_radius=args.bnd_region_radius,
975983
)
976984

977985

@@ -1030,6 +1038,13 @@ def get_parser():
10301038
default=300,
10311039
help="Radius of the initial seeds' sizes around signal that can constitute a candidate region seed.",
10321040
)
1041+
parser.add_argument(
1042+
"--bnd-region-radius",
1043+
type=int,
1044+
required=False,
1045+
default=300,
1046+
help="Margin (bp) added around BND signals when seeding candidate regions. Independent of --buffer-region-radius.",
1047+
)
10331048
parser.add_argument(
10341049
"--cutoff-median-readcount-per-region",
10351050
type=float,

src/svirlpool/localassembly/consensus.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,10 @@ def _rank_reads_by_similarity(
518518

519519

520520
def _rank_reads_by_alignment_lengths(
521-
pairwise_alignment_lengths_matrix: np.ndarray,
522-
sim_read_names: list[str],
523-
cluster_read_names: list[str]) -> list[str]:
521+
pairwise_alignment_lengths_matrix: np.ndarray,
522+
sim_read_names: list[str],
523+
cluster_read_names: list[str],
524+
) -> list[str]:
524525
"""
525526
rank all reads of the cluster by the sum of their alignment lengths to all other reads in the cluster. Largest sum is ranked highest.
526527
"""
@@ -531,7 +532,9 @@ def _rank_reads_by_alignment_lengths(
531532
cluster_indices = np.array([name_to_idx[r] for r in cluster_read_names])
532533

533534
# Sub-matrix for the cluster
534-
sub_sim = pairwise_alignment_lengths_matrix[np.ix_(cluster_indices, cluster_indices)]
535+
sub_sim = pairwise_alignment_lengths_matrix[
536+
np.ix_(cluster_indices, cluster_indices)
537+
]
535538
# sum alignment lengths of each read to all other reads in the cluster
536539
for i in range(sub_sim.shape[0]):
537540
sub_sim[i, i] = 0
@@ -541,6 +544,7 @@ def _rank_reads_by_alignment_lengths(
541544
# return the readnames sorted by sum of alignment lengths
542545
return [cluster_read_names[i] for i in sim_ranks]
543546

547+
544548
def find_representative_read(
545549
similarity_matrix: np.ndarray,
546550
pairwise_alignment_lengths_matrix: np.ndarray,
@@ -549,14 +553,21 @@ def find_representative_read(
549553
) -> str:
550554
"""Find the representative read of a cluster based on similarity and alignment lengths."""
551555
# rank reads by similarity and alignment lengths
552-
ranked_by_similarity = _rank_reads_by_similarity(similarity_matrix, sim_read_names, cluster_read_names)
553-
ranked_by_alignment_lengths = _rank_reads_by_alignment_lengths(pairwise_alignment_lengths_matrix, sim_read_names, cluster_read_names)
556+
ranked_by_similarity = _rank_reads_by_similarity(
557+
similarity_matrix, sim_read_names, cluster_read_names
558+
)
559+
ranked_by_alignment_lengths = _rank_reads_by_alignment_lengths(
560+
pairwise_alignment_lengths_matrix, sim_read_names, cluster_read_names
561+
)
554562
ranksums: np.ndarray = np.zeros(len(cluster_read_names))
555563
for i, read in enumerate(cluster_read_names):
556-
ranksums[i] = ranked_by_similarity.index(read) + ranked_by_alignment_lengths.index(read)
564+
ranksums[i] = ranked_by_similarity.index(
565+
read
566+
) + ranked_by_alignment_lengths.index(read)
557567
representative_read = cluster_read_names[np.argmin(ranksums)]
558568
return representative_read
559569

570+
560571
def make_consensus_with_racon_subsampled(
561572
reference_fasta: Path,
562573
sam_alignments: Path,
@@ -1171,7 +1182,9 @@ def partition_reads_spectral(
11711182
return {read: int(label) for read, label in zip(read_names, labels, strict=True)}
11721183

11731184

1174-
def pairwise_alignment_lengths(ava_alignments:list[pysam.AlignedSegment], read_names:list[str]) -> np.ndarray:
1185+
def pairwise_alignment_lengths(
1186+
ava_alignments: list[pysam.AlignedSegment], read_names: list[str]
1187+
) -> np.ndarray:
11751188
"""Calculate pairwise alignment lengths from all-vs-all alignments.
11761189
11771190
Args:
@@ -1472,7 +1485,7 @@ def consensus_while_clustering(
14721485
_pairwise_aln_lengths = pairwise_alignment_lengths(
14731486
ava_alignments=all_vs_all_alignments, read_names=sim_read_names
14741487
)
1475-
1488+
14761489
_representative = (
14771490
find_representative_read(
14781491
similarity_matrix=similarity_matrix,
@@ -3379,4 +3392,3 @@ def main():
33793392
if __name__ == "__main__":
33803393
main()
33813394
# %%
3382-

src/svirlpool/signalprocessing/signaldepths_to_signalstrength.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ def genomic_distance_score(d: int, flatness: float = 15.0) -> float:
6767

6868

6969
def delta(
70-
subj: npt.NDArray[np.int32], obj: npt.NDArray[np.int32], flatness: float
70+
subj: npt.NDArray[np.int32],
71+
obj: npt.NDArray[np.int32],
72+
flatness: float,
73+
bnd_proximity: int = 100,
74+
bnd_repeat_proximity: int = 500,
7175
) -> float:
7276
"""returns 1 if no distance between two loci, else x >= 0"""
7377
distance = np.uint32(2**31 - 1)
@@ -85,9 +89,9 @@ def delta(
8589
case (0, 2): # INS vs DELR
8690
pass
8791
case (0, 3): # INS vs BNDL
88-
distance = distance_collapsed_sv_signals(subj, obj)
92+
pass
8993
case (0, 4): # INS vs BNDR
90-
distance = distance_collapsed_sv_signals(subj, obj)
94+
pass
9195
case (1, 0): # DELL vs INS
9296
pass
9397
case (1, 1): # DELL vs DELL
@@ -98,7 +102,7 @@ def delta(
98102
case (1, 3): # DELL vs BNDL
99103
pass
100104
case (1, 4): # DELL vs BNDR
101-
distance = distance_collapsed_sv_signals(subj, obj)
105+
pass
102106
case (2, 0): # DELR vs INS
103107
pass
104108
case (2, 1): # DELR vs DELL
@@ -107,7 +111,7 @@ def delta(
107111
distance = distance_collapsed_sv_signals(subj, obj)
108112
size_score = size_similarity_score(subj, obj)
109113
case (2, 3): # DELR vs BNDL
110-
distance = distance_collapsed_sv_signals(subj, obj)
114+
pass
111115
case (2, 4): # DELR vs BNDR
112116
pass
113117
case (3, 0): # BNDL vs INS
@@ -118,6 +122,9 @@ def delta(
118122
pass
119123
case (3, 3): # BNDL vs BNDL
120124
distance = distance_collapsed_sv_signals(subj, obj)
125+
_in_same_repeat = subj[-2] == obj[-2] and int(subj[-2]) >= 0
126+
if distance > (bnd_repeat_proximity if _in_same_repeat else bnd_proximity):
127+
return 0.0
121128
case (3, 4): # BNDL vs BNDR
122129
pass
123130
case (4, 0): # BNDR vs INS
@@ -130,12 +137,15 @@ def delta(
130137
pass
131138
case (4, 4): # BNDR vs BNDR
132139
distance = distance_collapsed_sv_signals(subj, obj)
140+
_in_same_repeat = subj[-2] == obj[-2] and int(subj[-2]) >= 0
141+
if distance > (bnd_repeat_proximity if _in_same_repeat else bnd_proximity):
142+
return 0.0
133143
if distance < 0:
134144
return 1.0
135145
elif distance == 0:
136146
return 1.0
137147
else:
138-
distance_score = genomic_distance_score(distance, flatness)
148+
distance_score = genomic_distance_score(int(distance), flatness)
139149
return max(distance_score, size_score)
140150

141151

@@ -146,6 +156,8 @@ def pseudo_convolve(
146156
warning_collapsed_region_size: int = 2_000,
147157
high_density_threshold: int = 200,
148158
high_density_score: float = 10.0,
159+
bnd_proximity: int = 100,
160+
bnd_repeat_proximity: int = 500,
149161
) -> np.ndarray:
150162
"""convolves over a numpy array of signals and returns the sum of weighted distances
151163
by a kernel function (radius) which is dynamically expanded if it overlaps any repeats.
@@ -207,7 +219,13 @@ def pseudo_convolve(
207219
for l in range(j, k): # noqa: E741
208220
if l == i:
209221
continue
210-
next_val = delta(subj=np_signals[i], obj=np_signals[l], flatness=flatness)
222+
next_val = delta(
223+
subj=np_signals[i],
224+
obj=np_signals[l],
225+
flatness=flatness,
226+
bnd_proximity=bnd_proximity,
227+
bnd_repeat_proximity=bnd_repeat_proximity,
228+
)
211229
values[i] += next_val
212230

213231
i += 1
@@ -413,6 +431,8 @@ def signaldepths_to_signalstrength(
413431
kernel_signal_radius: int,
414432
slop_repeats_radius: int,
415433
tmp_dir_path: Path | None = None,
434+
bnd_proximity: int = 100,
435+
bnd_repeat_proximity: int = 500,
416436
) -> None:
417437
csv.field_size_limit(sys.maxsize)
418438
# --- load and prepare data --- #
@@ -520,6 +540,8 @@ def signaldepths_to_signalstrength(
520540
np_signals=np_signals_chr,
521541
kernelsize=kernel_signal_radius,
522542
flatness=flatness_distance,
543+
bnd_proximity=bnd_proximity,
544+
bnd_repeat_proximity=bnd_repeat_proximity,
523545
)
524546

525547
# Write results immediately
@@ -573,6 +595,8 @@ def run(args, **kwargs):
573595
kernel_signal_radius=args.kernel_signal_radius,
574596
threads=args.threads,
575597
slop_repeats_radius=args.slop_repeats_radius,
598+
bnd_proximity=args.bnd_proximity,
599+
bnd_repeat_proximity=args.bnd_repeat_proximity,
576600
)
577601

578602

@@ -635,6 +659,20 @@ def get_parser():
635659
default=50,
636660
help="Radius of slop for repeats.",
637661
)
662+
parser.add_argument(
663+
"--bnd-proximity",
664+
type=int,
665+
required=False,
666+
default=100,
667+
help="Maximum distance (bp) between two BND signals for them to score against each other.",
668+
)
669+
parser.add_argument(
670+
"--bnd-repeat-proximity",
671+
type=int,
672+
required=False,
673+
default=500,
674+
help="Maximum distance (bp) between two BND signals within the same tandem repeat for them to score against each other.",
675+
)
638676
return parser
639677

640678

0 commit comments

Comments
 (0)