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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: telometer
3
- Version: 1.0
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.0",
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",
@@ -1,4 +1,4 @@
1
- __version__ = '1.0'
1
+ __version__ = '1.01'
2
2
  __author__ = 'Santiago E Sanchez'
3
3
  __email__ = 'santy.esanchez@gmail.com'
4
4
  __license__ = 'MIT'
@@ -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=250, quality_threshold=15):
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])) # Assuming all motifs have similar lengths
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 # Window length must be odd
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, density_threshold=0.1, max_gap_length=150, quality_threshold=15):
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
- terminus_regions = [region for region in merged_telomere_regions if region[0] < 100 or region[1] > len(seq) - 100]
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
- # New function to process a single read
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'] # Extract base qualities here
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
- read_data = {
243
- 'query_name': read.query_name,
244
- 'is_unmapped': read.is_unmapped,
245
- 'is_reverse': read.is_reverse,
246
- 'reference_start': read.reference_start,
247
- 'reference_end': read.reference_end,
248
- 'reference_name': read.reference_name,
249
- 'mapping_quality': read.mapping_quality,
250
- 'query_sequence': read.query_sequence,
251
- 'query_qualities': read.query_qualities,
252
- 'reference_length': bam_file.get_reference_length(read.reference_name) if read.reference_name is not None else None
253
- }
254
-
255
-
256
- read_memory = estimate_memory_usage([read_data])
257
- batch_memory += read_memory
258
- batch.append(read_data)
259
-
260
-
261
- if batch_memory >= memory_limit_bytes:
262
- print(f"Processing batch of {len(batch)} reads, approx memory used: {batch_memory / (1024 ** 3):.2f} GB")
263
- process_batch(batch)
264
- batch_memory = 0
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=250, type=int, help='Maximum allowed gap length between telomere regions. Optional', required=False)
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.0
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