telometer 0.75__tar.gz → 0.78__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: 0.75
3
+ Version: 0.78
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="0.75",
5
+ version="0.78",
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,13 +1,12 @@
1
1
  #!/usr/bin/env python3
2
- # Telometer v0.75
3
- # Created by: Santiago E Sanchez
4
- # Artandi Lab, Stanford University, 2023
2
+ # Telometer v0.78
3
+ # Created by: Santiago E Sanche8
4
+ # Artandi Lab, Stanford University, 2024
5
5
  # Measures telomeres from ONT or PacBio long reads aligned to a T2T genome assembly
6
- # Simple Usage: telometer -b sorted_t2t.bam -o output.tsv
7
-
6
+ # Simple Usage: telometer -b sorted_t2t.bam -o output.ts8
8
7
  import pysam
9
8
  import re
10
- import regex
9
+ import regex as re
11
10
  import csv
12
11
  import argparse
13
12
  from multiprocessing import Pool, cpu_count
@@ -27,11 +26,11 @@ def get_adapters(chemistry):
27
26
  adapters_rc = [reverse_complement(adapter) for adapter in adapters]
28
27
  return adapters + adapters_rc
29
28
 
30
- def get_telomere_repeats():
31
- """Returns the telomere repeat sequences."""
32
- telomere_repeats = ['GGCCA', 'CCCTAA', 'TTAGGG', 'CCCTGG', 'CTTCTT', 'TTAAAA', 'CCTGG']
33
- telomere_repeats_rc = [reverse_complement(repeat) for repeat in telomere_repeats]
34
- return telomere_repeats + telomere_repeats_rc
29
+ def get_flexible_telomere_patterns():
30
+ """Returns flexible telomere repeat patterns for both G-rich and C-rich strands."""
31
+ g_rich_telomere_pattern = r'(T{1,2}T{1,2}A{1,2}G{1,2}G{1,2}G{1,2}){2,}'
32
+ c_rich_telomere_pattern = r'(C{1,2}C{1,2}C{1,2}T{1,2}A{1,2}A{1,2}){2,}'
33
+ return g_rich_telomere_pattern, c_rich_telomere_pattern
35
34
 
36
35
  def find_initial_boundary_region(sequence, patterns, max_mismatches):
37
36
  """Finds the initial boundary region with allowed mismatches."""
@@ -39,65 +38,78 @@ def find_initial_boundary_region(sequence, patterns, max_mismatches):
39
38
  combined_pattern = '|'.join(f'({pattern})' for pattern in patterns)
40
39
  regex_pattern = f'({combined_pattern}){{2,}}'
41
40
 
42
- for match in regex.finditer(f'({regex_pattern}){{e<={max_mismatches}}}', sequence, regex.BESTMATCH):
41
+ for match in re.finditer(f'({regex_pattern}){{e<={max_mismatches}}}', sequence, re.BESTMATCH):
43
42
  boundary_length = max(boundary_length, len(match.group(0)))
44
43
  return boundary_length
45
44
 
45
+ def determine_arm(reference_name, alignment_start, reference_length):
46
+ """Determine the chromosome arm based on the alignment start position and reference name."""
47
+ if alignment_start < 15000 and "q" not in reference_name:
48
+ return "p"
49
+ return "q"
50
+
46
51
  def process_read(args):
47
- read_data, telomere_repeats_re, adapters, minreadlen = args
52
+ read_data, g_rich_telomere_pattern, c_rich_telomere_pattern, adapters, minreadlen = args
48
53
 
49
54
  if read_data['is_unmapped'] or read_data['query_sequence'] is None or len(read_data['query_sequence']) < minreadlen:
55
+ print(f"Skipping read {read_data['read_id']}: unmapped or too short")
50
56
  return None
51
57
 
52
58
  alignment_start = read_data['reference_start']
53
59
  alignment_end = read_data['reference_end']
54
- seq = read_data['query_sequence']
60
+ seq_to_check = read_data['query_sequence']
55
61
 
56
62
  if read_data['is_reverse']:
57
63
  direction = "rev"
58
- seq = reverse_complement(seq)
64
+ seq_to_check = reverse_complement(seq_to_check)
59
65
  else:
60
66
  direction = "fwd"
61
67
 
62
68
  reference_genome_length = read_data['reference_length']
63
-
64
69
  if alignment_start >= 15000 and alignment_start <= reference_genome_length - 30000:
70
+ print(f"Skipping read {read_data['read_id']}: alignment start {alignment_start} within filtered range")
65
71
  return None
66
72
 
67
- if alignment_start < 15000 and "q" not in read_data['reference_name']:
68
- arm = "p"
69
- else:
70
- arm = "q"
73
+ arm = determine_arm(read_data['reference_name'], alignment_start, reference_genome_length)
74
+
75
+ telomere_starts = [m.start() for m in re.finditer(g_rich_telomere_pattern, seq_to_check)]
76
+ if not telomere_starts:
77
+ telomere_starts = [m.start() for m in re.finditer(c_rich_telomere_pattern, seq_to_check)]
71
78
 
72
- telomere_start = [m.start() for m in re.finditer(telomere_repeats_re, seq)]
73
- if telomere_start:
74
- telomere_start = telomere_start[0]
75
- if telomere_start > 100 and (len(seq) - telomere_start > 200):
76
- return None
79
+ if telomere_starts:
80
+ telomere_start = telomere_starts[0]
77
81
 
78
- telomere_end = min((seq.find(adapter) for adapter in adapters), default=-1)
79
- if telomere_end == -1:
80
- telomere_end = len(seq)
82
+ # Find the end of the telomere region
83
+ telomere_end = min((pos for pos in (seq_to_check.find(adapter) for adapter in adapters) if pos != -1), default=len(seq_to_check))
84
+
85
+ telomere_region = seq_to_check[telomere_start:telomere_end]
86
+ telomere_repeat = [m.group() for m in re.finditer(g_rich_telomere_pattern, telomere_region)]
87
+ if not telomere_repeat:
88
+ telomere_repeat = [m.group() for m in re.finditer(c_rich_telomere_pattern, telomere_region)]
81
89
 
82
- telomere_region = seq[telomere_start:telomere_end]
83
- telomere_repeat = [m.group() for m in re.finditer('|'.join(telomere_repeats_re.split('|')), telomere_region)]
84
90
  telomere_length = len(''.join(telomere_repeat))
85
91
 
86
- boundary_mm1_length = find_initial_boundary_region(telomere_region, telomere_repeats_re.split('|'), max_mismatches=1)
87
-
88
- return {
89
- 'chromosome': read_data['reference_name'],
90
- 'reference_start': alignment_start,
91
- 'reference_end': alignment_end,
92
- 'telomere_length': telomere_length,
93
- 'subtel_boundary_length': boundary_mm1_length,
94
- 'read_id': read_data['query_name'],
95
- 'mapping_quality': read_data['mapping_quality'],
96
- 'read_length': len(seq),
97
- 'arm': arm,
98
- 'direction': direction
99
- }
100
- return None
92
+ # Find the region immediately adjacent to the telomere region
93
+ boundary_mm1_region = seq_to_check[telomere_end:]
94
+ boundary_mm1_length = find_initial_boundary_region(boundary_mm1_region, g_rich_telomere_pattern.split('|') + c_rich_telomere_pattern.split('|'), max_mismatches=2)
95
+ else:
96
+ telomere_start = None
97
+ telomere_end = None
98
+ telomere_length = 0
99
+ boundary_mm1_length = 0
100
+
101
+ result = {
102
+ 'read_id': read_data['read_id'],
103
+ 'telomere_start': telomere_start,
104
+ 'telomere_end': telomere_end,
105
+ 'telomere_length': telomere_length,
106
+ 'boundary_mm1_length': boundary_mm1_length,
107
+ 'chromosome': read_data['reference_name'],
108
+ 'mapping_quality': read_data['mapping_quality'],
109
+ 'direction': direction,
110
+ 'arm': arm
111
+ }
112
+ return result
101
113
 
102
114
  def calculate_telomere_length():
103
115
  parser = argparse.ArgumentParser(description='Calculate telomere length from a BAM file.')
@@ -106,28 +118,27 @@ def calculate_telomere_length():
106
118
  parser.add_argument('-c', '--chemistry', default="r10", help="Sequencing chemistry (r9 or r10, default=r10). Optional", required=False)
107
119
  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)
108
120
  args = parser.parse_args()
109
- bam_file = pysam.AlignmentFile(args.bam, "rb")
110
121
 
111
122
  adapters = get_adapters(args.chemistry)
112
- telomere_repeats = get_telomere_repeats()
113
- telomere_repeats_re = "|".join(f'({repeat}){{2,}}' for repeat in telomere_repeats)
123
+ g_rich_telomere_pattern, c_rich_telomere_pattern = get_flexible_telomere_patterns()
114
124
 
125
+ bam_file = pysam.AlignmentFile(args.bam, "rb")
115
126
  read_data_list = [{
116
- 'query_name': read.query_name,
127
+ 'read_id': read.query_name,
117
128
  'is_unmapped': read.is_unmapped,
118
- 'is_reverse': read.is_reverse,
119
129
  'reference_start': read.reference_start,
120
130
  'reference_end': read.reference_end,
121
131
  'reference_name': read.reference_name,
122
132
  'mapping_quality': read.mapping_quality,
123
133
  'query_sequence': read.query_sequence,
124
- 'reference_length': bam_file.get_reference_length(read.reference_name)
125
- } for read in bam_file]
134
+ 'reference_length': bam_file.get_reference_length(read.reference_name) if read.reference_name is not None else None,
135
+ 'is_reverse': read.is_reverse
136
+ } for read in bam_file if read.reference_name is not None and read.reference_name != 'chrM']
126
137
 
127
138
  with Pool(processes=cpu_count()) as pool:
128
139
  results = pool.map(
129
140
  process_read,
130
- [(read_data, telomere_repeats_re, adapters, args.minreadlen) for read_data in read_data_list]
141
+ [(read_data, g_rich_telomere_pattern, c_rich_telomere_pattern, adapters, args.minreadlen) for read_data in read_data_list]
131
142
  )
132
143
 
133
144
  results = [result for result in results if result]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: telometer
3
- Version: 0.75
3
+ Version: 0.78
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