telometer 0.80__tar.gz → 0.81__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.80
3
+ Version: 0.81
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.80",
5
+ version="0.81",
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,12 +1,13 @@
1
1
  #!/usr/bin/env python3
2
- # Telometer v0.79
3
- # Created by: Santiago E Sanche8
4
- # Artandi Lab, Stanford University, 2024
2
+ # Telometer v0.81
3
+ # Created by: Santiago E Sanchez
4
+ # Artandi Lab, Stanford University, 2023
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.ts8
6
+ # Simple Usage: telometer -b sorted_t2t.bam -o output.tsv
7
+
7
8
  import pysam
8
9
  import re
9
- import regex as re
10
+ import regex
10
11
  import csv
11
12
  import argparse
12
13
  from multiprocessing import Pool, cpu_count
@@ -26,11 +27,11 @@ def get_adapters(chemistry):
26
27
  adapters_rc = [reverse_complement(adapter) for adapter in adapters]
27
28
  return adapters + adapters_rc
28
29
 
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
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
34
35
 
35
36
  def find_initial_boundary_region(sequence, patterns, max_mismatches):
36
37
  """Finds the initial boundary region with allowed mismatches."""
@@ -38,75 +39,65 @@ def find_initial_boundary_region(sequence, patterns, max_mismatches):
38
39
  combined_pattern = '|'.join(f'({pattern})' for pattern in patterns)
39
40
  regex_pattern = f'({combined_pattern}){{2,}}'
40
41
 
41
- for match in re.finditer(f'({regex_pattern}){{e<={max_mismatches}}}', sequence, re.BESTMATCH):
42
+ for match in regex.finditer(f'({regex_pattern}){{e<={max_mismatches}}}', sequence, regex.BESTMATCH):
42
43
  boundary_length = max(boundary_length, len(match.group(0)))
43
44
  return boundary_length
44
45
 
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
-
51
46
  def process_read(args):
52
- read_data, g_rich_telomere_pattern, c_rich_telomere_pattern, adapters, minreadlen = args
47
+ read_data, telomere_repeats_re, adapters, minreadlen = args
53
48
 
54
49
  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")
56
50
  return None
57
51
 
58
52
  alignment_start = read_data['reference_start']
59
53
  alignment_end = read_data['reference_end']
60
- seq_to_check = read_data['query_sequence']
54
+ seq = read_data['query_sequence']
61
55
 
62
56
  if read_data['is_reverse']:
63
57
  direction = "rev"
64
- seq_to_check = reverse_complement(seq_to_check)
58
+ seq = reverse_complement(seq)
65
59
  else:
66
60
  direction = "fwd"
67
61
 
68
62
  reference_genome_length = read_data['reference_length']
63
+
69
64
  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")
71
65
  return None
72
66
 
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)]
78
-
79
- if telomere_starts:
80
- telomere_start = telomere_starts[0]
67
+ if alignment_start < 15000 and "q" not in read_data['reference_name']:
68
+ arm = "p"
69
+ else:
70
+ arm = "q"
81
71
 
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))
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
84
77
 
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)]
78
+ telomere_end = min((seq.find(adapter) for adapter in adapters), default=-1)
79
+ if telomere_end == -1:
80
+ telomere_end = len(seq)
89
81
 
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)]
90
84
  telomere_length = len(''.join(telomere_repeat))
91
- if telomere_length > 0:
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
- result = {
96
- 'chromosome': read_data['reference_name'],
97
- 'arm': arm,
98
- 'telomere_start': telomere_start,
99
- 'telomere_end': telomere_end,
100
- 'telomere_length': telomere_length,
101
- 'subtel_boundary_length': boundary_mm1_length,
102
- 'read_id': read_data['read_id'],
103
- 'mapping_quality': read_data['mapping_quality'],
104
- 'direction': direction
105
- }
106
- return result
107
- else:
108
- return None
109
85
 
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
110
101
 
111
102
  def calculate_telomere_length():
112
103
  parser = argparse.ArgumentParser(description='Calculate telomere length from a BAM file.')
@@ -115,27 +106,28 @@ def calculate_telomere_length():
115
106
  parser.add_argument('-c', '--chemistry', default="r10", help="Sequencing chemistry (r9 or r10, default=r10). Optional", required=False)
116
107
  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)
117
108
  args = parser.parse_args()
109
+ bam_file = pysam.AlignmentFile(args.bam, "rb")
118
110
 
119
111
  adapters = get_adapters(args.chemistry)
120
- g_rich_telomere_pattern, c_rich_telomere_pattern = get_flexible_telomere_patterns()
112
+ telomere_repeats = get_telomere_repeats()
113
+ telomere_repeats_re = "|".join(f'({repeat}){{2,}}' for repeat in telomere_repeats)
121
114
 
122
- bam_file = pysam.AlignmentFile(args.bam, "rb")
123
115
  read_data_list = [{
124
- 'read_id': read.query_name,
116
+ 'query_name': read.query_name,
125
117
  'is_unmapped': read.is_unmapped,
118
+ 'is_reverse': read.is_reverse,
126
119
  'reference_start': read.reference_start,
127
120
  'reference_end': read.reference_end,
128
121
  'reference_name': read.reference_name,
129
122
  'mapping_quality': read.mapping_quality,
130
123
  'query_sequence': read.query_sequence,
131
- 'reference_length': bam_file.get_reference_length(read.reference_name) if read.reference_name is not None else None,
132
- 'is_reverse': read.is_reverse
124
+ 'reference_length': bam_file.get_reference_length(read.reference_name) if read.reference_name is not None else None
133
125
  } for read in bam_file if read.reference_name is not None and read.reference_name != 'chrM']
134
126
 
135
127
  with Pool(processes=cpu_count()) as pool:
136
128
  results = pool.map(
137
129
  process_read,
138
- [(read_data, g_rich_telomere_pattern, c_rich_telomere_pattern, adapters, args.minreadlen) for read_data in read_data_list]
130
+ [(read_data, telomere_repeats_re, adapters, args.minreadlen) for read_data in read_data_list]
139
131
  )
140
132
 
141
133
  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.80
3
+ Version: 0.81
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