telometer 1.0__tar.gz → 1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {telometer-1.0 → telometer-1.1}/PKG-INFO +1 -1
- {telometer-1.0 → telometer-1.1}/setup.py +1 -1
- {telometer-1.0 → telometer-1.1}/telometer/__init__.py +1 -1
- {telometer-1.0 → telometer-1.1}/telometer/telometer.py +85 -52
- {telometer-1.0 → telometer-1.1}/telometer.egg-info/PKG-INFO +1 -1
- {telometer-1.0 → telometer-1.1}/LICENSE.txt +0 -0
- {telometer-1.0 → telometer-1.1}/README.md +0 -0
- {telometer-1.0 → telometer-1.1}/setup.cfg +0 -0
- {telometer-1.0 → telometer-1.1}/telometer.egg-info/SOURCES.txt +0 -0
- {telometer-1.0 → telometer-1.1}/telometer.egg-info/dependency_links.txt +0 -0
- {telometer-1.0 → telometer-1.1}/telometer.egg-info/entry_points.txt +0 -0
- {telometer-1.0 → telometer-1.1}/telometer.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: telometer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1
|
|
4
4
|
Summary: a simple regular expression based method for measuring individual, chromosome-specific telomere lengths from long-read sequencing data
|
|
5
5
|
Author: Santiago E Sanchez
|
|
6
6
|
Author-email: ses94@stanford.edu
|
|
@@ -2,7 +2,7 @@ import setuptools
|
|
|
2
2
|
|
|
3
3
|
setuptools.setup(
|
|
4
4
|
name="telometer",
|
|
5
|
-
version="1.
|
|
5
|
+
version="1.01",
|
|
6
6
|
author="Santiago E Sanchez",
|
|
7
7
|
author_email="ses94@stanford.edu",
|
|
8
8
|
description="a simple regular expression based method for measuring individual, chromosome-specific telomere lengths from long-read sequencing data",
|
|
@@ -24,9 +24,8 @@ def get_telomere_repeats():
|
|
|
24
24
|
telomere_repeats_rc = [reverse_complement(repeat) for repeat in telomere_repeats]
|
|
25
25
|
return telomere_repeats + telomere_repeats_rc
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
def identify_telomere_regions(seq, base_qualities, telomere_motifs, window_size=120, step_size=12,
|
|
29
|
-
density_threshold=0.1, max_gap_length=
|
|
28
|
+
density_threshold=0.1, max_gap_length=20, quality_threshold=15):
|
|
30
29
|
telomere_regions = []
|
|
31
30
|
current_start = None
|
|
32
31
|
gap_length = 0
|
|
@@ -38,7 +37,7 @@ def identify_telomere_regions(seq, base_qualities, telomere_motifs, window_size=
|
|
|
38
37
|
for i in range(0, len(seq) - window_size + 1, step_size):
|
|
39
38
|
window_seq = seq[i:i + window_size]
|
|
40
39
|
motif_count = len(re.findall(combined_motif_pattern, window_seq))
|
|
41
|
-
density = motif_count / (window_size / len(telomere_motifs[0]))
|
|
40
|
+
density = motif_count / (window_size / len(telomere_motifs[0]))
|
|
42
41
|
densities.append(density)
|
|
43
42
|
|
|
44
43
|
if density >= density_threshold:
|
|
@@ -49,7 +48,6 @@ def identify_telomere_regions(seq, base_qualities, telomere_motifs, window_size=
|
|
|
49
48
|
if current_start is not None:
|
|
50
49
|
gap_length += step_size
|
|
51
50
|
|
|
52
|
-
|
|
53
51
|
gap_start = i - gap_length
|
|
54
52
|
gap_end = i
|
|
55
53
|
gap_quality = base_qualities[gap_start:gap_end]
|
|
@@ -59,19 +57,15 @@ def identify_telomere_regions(seq, base_qualities, telomere_motifs, window_size=
|
|
|
59
57
|
else:
|
|
60
58
|
avg_gap_quality = 0
|
|
61
59
|
|
|
62
|
-
|
|
63
60
|
if avg_gap_quality < quality_threshold:
|
|
64
61
|
gap_length = 0
|
|
65
62
|
continue
|
|
66
63
|
|
|
67
|
-
|
|
68
64
|
if gap_length > max_gap_length:
|
|
69
|
-
|
|
70
65
|
mismatch_motif_count = len(re.findall(f"({combined_motif_pattern}){{e<=1}}", window_seq))
|
|
71
66
|
mismatch_density = mismatch_motif_count / (window_size / len(telomere_motifs[0]))
|
|
72
67
|
|
|
73
68
|
if mismatch_density >= density_threshold:
|
|
74
|
-
|
|
75
69
|
gap_length = 0
|
|
76
70
|
else:
|
|
77
71
|
has_large_gap = True
|
|
@@ -84,28 +78,20 @@ def identify_telomere_regions(seq, base_qualities, telomere_motifs, window_size=
|
|
|
84
78
|
|
|
85
79
|
return telomere_regions, densities, has_large_gap, combined_motif_pattern
|
|
86
80
|
|
|
87
|
-
|
|
88
81
|
def detect_discontinuities(base_qualities, window_length=11, polyorder=2, gradient_threshold=-1):
|
|
89
|
-
|
|
90
82
|
if len(base_qualities) < window_length:
|
|
91
|
-
window_length = len(base_qualities) if len(base_qualities) % 2 == 1 else len(base_qualities) - 1
|
|
92
|
-
|
|
83
|
+
window_length = len(base_qualities) if len(base_qualities) % 2 == 1 else len(base_qualities) - 1
|
|
93
84
|
|
|
94
85
|
if len(base_qualities) > polyorder:
|
|
95
86
|
smoothed_qualities = savgol_filter(base_qualities, window_length=window_length, polyorder=polyorder)
|
|
96
87
|
else:
|
|
97
|
-
|
|
98
88
|
smoothed_qualities = base_qualities
|
|
99
89
|
|
|
100
|
-
|
|
101
90
|
gradient = np.diff(smoothed_qualities)
|
|
102
|
-
|
|
103
|
-
|
|
104
91
|
discontinuities = np.where(gradient < gradient_threshold)[0]
|
|
105
92
|
|
|
106
93
|
return discontinuities, smoothed_qualities
|
|
107
94
|
|
|
108
|
-
|
|
109
95
|
def check_gap(seq, base_qualities, telomere_regions, gradient_threshold=-1, quality_threshold=15):
|
|
110
96
|
merged_regions = []
|
|
111
97
|
prev_end = None
|
|
@@ -129,24 +115,37 @@ def check_gap(seq, base_qualities, telomere_regions, gradient_threshold=-1, qual
|
|
|
129
115
|
|
|
130
116
|
return merged_regions
|
|
131
117
|
|
|
132
|
-
def measure_telomere_length(seq, telomere_motifs, base_qualities, window_size=120, step_size=12,
|
|
118
|
+
def measure_telomere_length(seq, telomere_motifs, base_qualities, window_size=120, step_size=12,
|
|
119
|
+
density_threshold=0.1, max_gap_length=20, quality_threshold=15):
|
|
120
|
+
|
|
133
121
|
telomere_regions, densities, has_large_gap, combined_motif_pattern = identify_telomere_regions(
|
|
134
122
|
seq, base_qualities, telomere_motifs, window_size, step_size, density_threshold, max_gap_length, quality_threshold)
|
|
135
123
|
|
|
136
124
|
if not telomere_regions:
|
|
137
125
|
return 0, 0, 0, densities, has_large_gap, combined_motif_pattern
|
|
138
126
|
|
|
127
|
+
|
|
128
|
+
if not any(density >= 0.75 for density in densities):
|
|
129
|
+
return 0, 0, 0, densities, has_large_gap, combined_motif_pattern
|
|
130
|
+
|
|
131
|
+
|
|
139
132
|
merged_telomere_regions = check_gap(seq, base_qualities, telomere_regions, gradient_threshold=-1, quality_threshold=quality_threshold)
|
|
140
|
-
|
|
133
|
+
|
|
134
|
+
terminus_regions = []
|
|
135
|
+
for start, end in merged_telomere_regions:
|
|
136
|
+
if start < 100 or end > len(seq) - 100:
|
|
137
|
+
terminus_regions.append((start, end))
|
|
141
138
|
|
|
142
139
|
if not terminus_regions:
|
|
143
140
|
return 0, 0, 0, densities, has_large_gap, combined_motif_pattern
|
|
144
141
|
|
|
142
|
+
|
|
145
143
|
telomere_start, telomere_end = terminus_regions[0]
|
|
146
144
|
telomere_length = telomere_end - telomere_start
|
|
145
|
+
|
|
147
146
|
return telomere_start, telomere_end, telomere_length, densities, has_large_gap, combined_motif_pattern
|
|
148
147
|
|
|
149
|
-
|
|
148
|
+
|
|
150
149
|
def process_read(read_data, telomere_motifs, max_gap_length, min_read_len):
|
|
151
150
|
if read_data['is_unmapped'] or read_data['reference_name'] == 'chrM':
|
|
152
151
|
return None
|
|
@@ -155,7 +154,7 @@ def process_read(read_data, telomere_motifs, max_gap_length, min_read_len):
|
|
|
155
154
|
return None
|
|
156
155
|
|
|
157
156
|
seq = read_data['query_sequence']
|
|
158
|
-
base_qualities = read_data['query_qualities']
|
|
157
|
+
base_qualities = read_data['query_qualities']
|
|
159
158
|
if seq is None or len(seq) < min_read_len:
|
|
160
159
|
return None
|
|
161
160
|
|
|
@@ -170,13 +169,15 @@ def process_read(read_data, telomere_motifs, max_gap_length, min_read_len):
|
|
|
170
169
|
|
|
171
170
|
direction = "rev" if read_data['is_reverse'] else "fwd"
|
|
172
171
|
|
|
173
|
-
|
|
174
172
|
telomere_start, telomere_end, telomere_length, densities, has_large_gap, combined_motif_pattern = measure_telomere_length(
|
|
175
173
|
seq, telomere_motifs, base_qualities, max_gap_length=max_gap_length)
|
|
176
174
|
|
|
177
175
|
if telomere_length < 1:
|
|
178
176
|
return None
|
|
179
177
|
|
|
178
|
+
if (telomere_length + 50) > len(seq):
|
|
179
|
+
return None
|
|
180
|
+
|
|
180
181
|
return {
|
|
181
182
|
'chromosome': read_data['reference_name'],
|
|
182
183
|
'reference_start': alignment_start,
|
|
@@ -189,7 +190,6 @@ def process_read(read_data, telomere_motifs, max_gap_length, min_read_len):
|
|
|
189
190
|
'direction': direction
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
|
|
193
193
|
def process_read_wrapper(args):
|
|
194
194
|
return process_read(*args)
|
|
195
195
|
|
|
@@ -205,8 +205,7 @@ def estimate_memory_usage(reads_list):
|
|
|
205
205
|
memory_usage += sys.getsizeof(read['query_name']) + seq_len + quality_len + sys.getsizeof(read)
|
|
206
206
|
return memory_usage
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
def process_bam_file(bam_file_path, output_file_path, max_gap_length=150, min_read_len=1000, num_processes=8, memory_limit_gb=8):
|
|
208
|
+
def process_bam_file(bam_file_path, output_file_path, max_gap_length=20, min_read_len=1000, num_processes=8, memory_limit_gb=8):
|
|
210
209
|
start_time = time.time()
|
|
211
210
|
bam_file = pysam.AlignmentFile(bam_file_path, "rb")
|
|
212
211
|
telomere_motifs = get_telomere_repeats()
|
|
@@ -239,30 +238,39 @@ def process_bam_file(bam_file_path, output_file_path, max_gap_length=150, min_re
|
|
|
239
238
|
|
|
240
239
|
for read in bam_file:
|
|
241
240
|
if read.reference_name is not None and read.reference_name != 'chrM':
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
241
|
+
base_qualities = read.query_qualities
|
|
242
|
+
|
|
243
|
+
if base_qualities is not None and len(base_qualities) > 0:
|
|
244
|
+
avg_phred_score = sum(base_qualities) / len(base_qualities)
|
|
245
|
+
else:
|
|
246
|
+
avg_phred_score = 0
|
|
247
|
+
#print("no phred score")
|
|
248
|
+
|
|
249
|
+
if avg_phred_score > 9:
|
|
250
|
+
read_data = {
|
|
251
|
+
'query_name': read.query_name,
|
|
252
|
+
'is_unmapped': read.is_unmapped,
|
|
253
|
+
'is_reverse': read.is_reverse,
|
|
254
|
+
'reference_start': read.reference_start,
|
|
255
|
+
'reference_end': read.reference_end,
|
|
256
|
+
'reference_name': read.reference_name,
|
|
257
|
+
'mapping_quality': read.mapping_quality,
|
|
258
|
+
'query_sequence': read.query_sequence,
|
|
259
|
+
'query_qualities': base_qualities,
|
|
260
|
+
'reference_length': bam_file.get_reference_length(read.reference_name) if read.reference_name is not None else None
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
read_memory = estimate_memory_usage([read_data])
|
|
264
|
+
batch_memory += read_memory
|
|
265
|
+
batch.append(read_data)
|
|
266
|
+
|
|
267
|
+
if batch_memory >= memory_limit_bytes:
|
|
268
|
+
print(f"Processing batch of {len(batch)} reads, approx memory used: {batch_memory / (1024 ** 3):.2f} GB")
|
|
269
|
+
process_batch(batch)
|
|
270
|
+
batch_memory = 0
|
|
271
|
+
batch = []
|
|
272
|
+
#else:
|
|
273
|
+
#print("low phred")
|
|
266
274
|
|
|
267
275
|
if batch:
|
|
268
276
|
print(f"Processing final batch of {len(batch)} reads")
|
|
@@ -270,12 +278,36 @@ def process_bam_file(bam_file_path, output_file_path, max_gap_length=150, min_re
|
|
|
270
278
|
|
|
271
279
|
bam_file.close()
|
|
272
280
|
|
|
273
|
-
|
|
274
281
|
results_df = pd.DataFrame(list(read_results.values()))
|
|
275
282
|
results_df.to_csv(output_file_path, sep='\t', index=False)
|
|
276
283
|
|
|
277
284
|
print(f"Telometer completed successfully. Total telomeres measured: {len(read_results)}")
|
|
278
285
|
print(f"Total processing time: {time.time() - start_time:.2f} seconds")
|
|
286
|
+
# After the final print statements
|
|
287
|
+
if len(read_results) > 0:
|
|
288
|
+
# Convert read results to a DataFrame for easy statistical computation
|
|
289
|
+
results_df = pd.DataFrame(list(read_results.values()))
|
|
290
|
+
|
|
291
|
+
# Calculate summary statistics for the 'telomere_length' column
|
|
292
|
+
telomere_lengths = results_df['telomere_length']
|
|
293
|
+
|
|
294
|
+
min_length = telomere_lengths.min()
|
|
295
|
+
percentile_25 = telomere_lengths.quantile(0.25)
|
|
296
|
+
median_length = telomere_lengths.median()
|
|
297
|
+
mean_length = telomere_lengths.mean()
|
|
298
|
+
percentile_75 = telomere_lengths.quantile(0.75)
|
|
299
|
+
max_length = telomere_lengths.max()
|
|
300
|
+
|
|
301
|
+
# Print summary statistics
|
|
302
|
+
print("\nTelomere Length Summary Statistics:")
|
|
303
|
+
print(f"Min: {min_length:.2f}")
|
|
304
|
+
print(f"25th Percentile: {percentile_25:.2f}")
|
|
305
|
+
print(f"Median: {median_length:.2f}")
|
|
306
|
+
print(f"Mean: {mean_length:.2f}")
|
|
307
|
+
print(f"75th Percentile: {percentile_75:.2f}")
|
|
308
|
+
print(f"Max: {max_length:.2f}")
|
|
309
|
+
else:
|
|
310
|
+
print("No telomere measurements to summarize.")
|
|
279
311
|
|
|
280
312
|
|
|
281
313
|
def run_telometer():
|
|
@@ -283,10 +315,11 @@ def run_telometer():
|
|
|
283
315
|
parser.add_argument('-b', '--bam', help='The path to the sorted BAM file.', required=True)
|
|
284
316
|
parser.add_argument('-o', '--output', help='The path to the output file.', required=True)
|
|
285
317
|
parser.add_argument('-m', '--minreadlen', default=1000, type=int, help='Minimum read length to consider (Default: 1000 for telomere capture, use 4000 for WGS). Optional', required=False)
|
|
286
|
-
parser.add_argument('-g', '--maxgaplen', default=
|
|
318
|
+
parser.add_argument('-g', '--maxgaplen', default=20, type=int, help='Maximum allowed gap length between telomere regions. Optional', required=False)
|
|
287
319
|
parser.add_argument('-t', '--threads', default=cpu_count(), type=int, help='Number of processing threads to use. Optional', required=False)
|
|
320
|
+
parser.add_argument('-l', '--memlimit', default=8, type=int, help="Maximum amount of memory to commit per batch of reads while processing. Optional, default = 8 Gb", required=False)
|
|
288
321
|
args = parser.parse_args()
|
|
289
|
-
process_bam_file(args.bam, args.output, max_gap_length=args.maxgaplen, min_read_len=args.minreadlen, num_processes=args.threads)
|
|
322
|
+
process_bam_file(args.bam, args.output, max_gap_length=args.maxgaplen, min_read_len=args.minreadlen, num_processes=args.threads, memory_limit_gb=args.memlimit)
|
|
290
323
|
|
|
291
324
|
if __name__ == "__main__":
|
|
292
325
|
run_telometer()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: telometer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1
|
|
4
4
|
Summary: a simple regular expression based method for measuring individual, chromosome-specific telomere lengths from long-read sequencing data
|
|
5
5
|
Author: Santiago E Sanchez
|
|
6
6
|
Author-email: ses94@stanford.edu
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|