Coverage for src / optwps / weighting.py: 81%
48 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-26 00:25 +0200
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-26 00:25 +0200
1import pysam
2from typing import Union
3import numpy as np
4import joblib
6from .read_processing import (
7 collect_fragment_features,
8 fragment_features,
9 iter_pysam_reads,
10 read_info_batches,
11 weight_from_features,
12)
15class WeightsCalculator:
16 """Calculates correction weights for WPS based on fragment features.
17 Weights are computed based on the distribution of fragment features in the dataset,
18 allowing for correction of biases in WPS calculations.
19 The weights are determined by binning the fragment features and calculating the inverse
20 of the frequency of fragments in each bin, which can then be applied to adjust WPS
21 scores accordingly.
22 """
24 def __init__(
25 self,
26 mappability_file: str = None,
27 nbins=10,
28 subsample=0.05,
29 min_insert_size=None,
30 max_insert_size=None,
31 min_mappability_threshold=0.9,
32 prior_count=20.0,
33 min_weight=0.2,
34 max_weight=5.0,
35 njobs=1,
36 read_buffer_size=10000,
37 ):
38 self.subsample = subsample
39 self.nbins = nbins
40 self.mappability_file = mappability_file
41 self.min_insert_size = min_insert_size
42 self.max_insert_size = max_insert_size
43 self.min_mappability_threshold = min_mappability_threshold
44 self.prior_count = prior_count
45 self.min_weight = min_weight
46 self.max_weight = max_weight
47 self.njobs = joblib.cpu_count() + njobs if njobs < 0 else njobs
48 self.njobs = max(1, self.njobs)
49 self.read_buffer_size = read_buffer_size
50 self.bin_edges = None
51 self.weights = None
53 def fit(self, bam: Union[str, pysam.AlignmentFile]):
54 close_bam = False
55 if isinstance(bam, str):
56 bam = (
57 pysam.AlignmentFile(bam, "rb")
58 if self.njobs == 1
59 else pysam.AlignmentFile(bam, "rb", threads=self.njobs)
60 )
61 close_bam = True
62 features = collect_fragment_features(
63 read_info_batches(iter_pysam_reads(bam.fetch()), self.read_buffer_size),
64 min_insert_size=self.min_insert_size,
65 max_insert_size=self.max_insert_size,
66 mappability_path=self.mappability_file,
67 min_mappability_threshold=self.min_mappability_threshold,
68 downsample_ratio=self.subsample,
69 njobs=self.njobs,
70 )
71 if close_bam:
72 bam.close()
73 if not features:
74 self.bin_edges = None
75 self.weights = None
76 return self
77 histogram, bin_edges = np.histogramdd(np.array(features), bins=self.nbins)
78 self.weights = np.ones_like(histogram, dtype=float)
79 observed = histogram > 0
80 mean_count = np.mean(histogram[observed])
81 self.weights[observed] = (mean_count + self.prior_count) / (
82 histogram[observed] + self.prior_count
83 )
84 if self.min_weight is not None or self.max_weight is not None:
85 self.weights[observed] = np.clip(
86 self.weights[observed],
87 -np.inf if self.min_weight is None else self.min_weight,
88 np.inf if self.max_weight is None else self.max_weight,
89 )
90 self.bin_edges = bin_edges
91 return self
93 def transform_features(self, features):
94 return weight_from_features(features, self.bin_edges, self.weights)
96 def transform(self, read: pysam.AlignedSegment):
97 if self.weights is None:
98 return 1.0
99 return self.transform_features(fragment_features(read))