SLRanger 0.0.1.9__py3-none-any.whl
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.
- SLRanger/SL_detect.py +744 -0
- SLRanger/__init__.py +1 -0
- SLRanger/add_gene.py +149 -0
- SLRanger/operon_predict.py +781 -0
- SLRanger/run_ex_function.py +52 -0
- SLRanger/visualization.py +412 -0
- slranger-0.0.1.9.data/scripts/SL_detect.py +744 -0
- slranger-0.0.1.9.data/scripts/add_gene.py +149 -0
- slranger-0.0.1.9.data/scripts/operon_predict.py +781 -0
- slranger-0.0.1.9.dist-info/METADATA +216 -0
- slranger-0.0.1.9.dist-info/RECORD +13 -0
- slranger-0.0.1.9.dist-info/WHEEL +5 -0
- slranger-0.0.1.9.dist-info/top_level.txt +1 -0
SLRanger/SL_detect.py
ADDED
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import re
|
|
3
|
+
import argparse
|
|
4
|
+
import pysam
|
|
5
|
+
import pandas as pd
|
|
6
|
+
# from ssw import AlignmentMgr
|
|
7
|
+
from pyssw.ssw_wrap import Aligner
|
|
8
|
+
from Bio.Seq import Seq
|
|
9
|
+
import random
|
|
10
|
+
import multiprocessing
|
|
11
|
+
from tqdm import tqdm
|
|
12
|
+
import time
|
|
13
|
+
from SLRanger.visualization import visualize_html
|
|
14
|
+
|
|
15
|
+
def fasta_to_dict(fasta_path):
|
|
16
|
+
fasta_dict = {}
|
|
17
|
+
with open(fasta_path, 'r') as f:
|
|
18
|
+
current_key = None
|
|
19
|
+
sequence_parts = []
|
|
20
|
+
for line in f:
|
|
21
|
+
line = line.strip()
|
|
22
|
+
if line.startswith('>'):
|
|
23
|
+
if current_key:
|
|
24
|
+
# 保存上一个序列
|
|
25
|
+
fasta_dict[current_key] = ''.join(sequence_parts)
|
|
26
|
+
current_key = line[1:] # 去掉 '>'
|
|
27
|
+
sequence_parts = []
|
|
28
|
+
else:
|
|
29
|
+
sequence_parts.append(line)
|
|
30
|
+
# 保存最后一个序列
|
|
31
|
+
if current_key:
|
|
32
|
+
fasta_dict[current_key] = ''.join(sequence_parts)
|
|
33
|
+
return fasta_dict
|
|
34
|
+
|
|
35
|
+
def generate_mismatches(kmer, bases=['A', 'T', 'C', 'G']):
|
|
36
|
+
mismatches = set()
|
|
37
|
+
for i in range(len(kmer)):
|
|
38
|
+
for base in bases:
|
|
39
|
+
if base != kmer[i]:
|
|
40
|
+
mismatched_kmer = kmer[:i] + base + kmer[i + 1:]
|
|
41
|
+
mismatches.add(mismatched_kmer)
|
|
42
|
+
return mismatches
|
|
43
|
+
|
|
44
|
+
def build_mismatch_index(sequences, k):
|
|
45
|
+
mismatch_to_kmer = {}
|
|
46
|
+
|
|
47
|
+
for seq_id, seq in sequences.items():
|
|
48
|
+
for i in range(len(seq) - k + 1):
|
|
49
|
+
kmer = seq[i:i + k]
|
|
50
|
+
mismatches = generate_mismatches(kmer)
|
|
51
|
+
for mismatch in mismatches:
|
|
52
|
+
if mismatch not in mismatch_to_kmer:
|
|
53
|
+
mismatch_to_kmer[mismatch] = kmer
|
|
54
|
+
|
|
55
|
+
return mismatch_to_kmer
|
|
56
|
+
|
|
57
|
+
def get_sequences_by_length(dictionary):
|
|
58
|
+
# 按键排序
|
|
59
|
+
sorted_keys = sorted(dictionary.keys())
|
|
60
|
+
# 用于跟踪已处理的长度
|
|
61
|
+
seen_lengths = set()
|
|
62
|
+
# 结果字典
|
|
63
|
+
result = {}
|
|
64
|
+
|
|
65
|
+
# 遍历排序后的键
|
|
66
|
+
for key in sorted_keys:
|
|
67
|
+
sequence = dictionary[key]
|
|
68
|
+
seq_length = len(sequence)
|
|
69
|
+
# 如果该长度尚未处理,添加到结果
|
|
70
|
+
if seq_length not in seen_lengths:
|
|
71
|
+
result[key] = {
|
|
72
|
+
'sequence': sequence, # 原始序列
|
|
73
|
+
'length': seq_length # 序列长度
|
|
74
|
+
}
|
|
75
|
+
seen_lengths.add(seq_length)
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
def extract_kmers(sequences, k):
|
|
80
|
+
kmer_id = {}
|
|
81
|
+
# 遍历每个序列并为每个5-mer分配ID
|
|
82
|
+
for key, seq in sequences.items():
|
|
83
|
+
encoded_seq = []
|
|
84
|
+
for i in range(len(seq) - k + 1):
|
|
85
|
+
kmer = seq[i:i+k]
|
|
86
|
+
encoded_seq.append(kmer)
|
|
87
|
+
kmer_id[key] = encoded_seq
|
|
88
|
+
return kmer_id
|
|
89
|
+
|
|
90
|
+
def find_matches(query, mismatch_to_kmer, kmer_to_refs, k):
|
|
91
|
+
query_kmers = [query[i:i + k] for i in range(len(query) - k + 1)]
|
|
92
|
+
matches = []
|
|
93
|
+
|
|
94
|
+
for query_kmer in query_kmers:
|
|
95
|
+
if query_kmer in mismatch_to_kmer:
|
|
96
|
+
original_kmer = mismatch_to_kmer[query_kmer]
|
|
97
|
+
if original_kmer in kmer_to_refs:
|
|
98
|
+
match_info = kmer_to_refs[original_kmer]
|
|
99
|
+
matches.extend(match_info)
|
|
100
|
+
|
|
101
|
+
return matches
|
|
102
|
+
|
|
103
|
+
def longest_consecutive(nums):
|
|
104
|
+
num_set = set(nums)
|
|
105
|
+
longest_streak = 0
|
|
106
|
+
max_end_value = None
|
|
107
|
+
|
|
108
|
+
for num in num_set:
|
|
109
|
+
if num - 1 not in num_set:
|
|
110
|
+
current_num = num
|
|
111
|
+
current_streak = 1
|
|
112
|
+
|
|
113
|
+
while current_num + 1 in num_set:
|
|
114
|
+
current_num += 1
|
|
115
|
+
current_streak += 1
|
|
116
|
+
|
|
117
|
+
if current_streak > longest_streak:
|
|
118
|
+
longest_streak = current_streak
|
|
119
|
+
max_end_value = current_num
|
|
120
|
+
|
|
121
|
+
return longest_streak, max_end_value
|
|
122
|
+
|
|
123
|
+
def find_best_match(query, mismatch_to_kmer, kmer_to_refs, k):
|
|
124
|
+
encoded_query = [query[i:i+k] for i in range(len(query) - k + 1)]
|
|
125
|
+
ref_positions = []
|
|
126
|
+
max_consecutive = 0
|
|
127
|
+
best_consecutive_end = 0
|
|
128
|
+
|
|
129
|
+
for query_kmer in encoded_query:
|
|
130
|
+
if query_kmer in kmer_to_refs:
|
|
131
|
+
if kmer_to_refs.count(query_kmer) > 1:
|
|
132
|
+
ref_positions.extend(index for index, kmer in enumerate(kmer_to_refs) if kmer == query_kmer)
|
|
133
|
+
else:
|
|
134
|
+
ref_positions.append(kmer_to_refs.index(query_kmer))
|
|
135
|
+
elif query_kmer in mismatch_to_kmer:
|
|
136
|
+
original_kmer = mismatch_to_kmer[query_kmer]
|
|
137
|
+
if original_kmer in kmer_to_refs:
|
|
138
|
+
if kmer_to_refs.count(query_kmer) > 1:
|
|
139
|
+
ref_positions.extend(index for index, kmer in enumerate(kmer_to_refs) if kmer == original_kmer)
|
|
140
|
+
else:
|
|
141
|
+
ref_positions.append(kmer_to_refs.index(original_kmer))
|
|
142
|
+
max_intersection = len(set(ref_positions))
|
|
143
|
+
if max_intersection > 0:
|
|
144
|
+
max_consecutive, best_consecutive_end = longest_consecutive(ref_positions)
|
|
145
|
+
|
|
146
|
+
if max_consecutive > len(query) - k:
|
|
147
|
+
# if max_intersection > max_consecutive:
|
|
148
|
+
max_intersection = max_consecutive
|
|
149
|
+
|
|
150
|
+
return max_intersection, max_consecutive, best_consecutive_end
|
|
151
|
+
|
|
152
|
+
def soft_processed(sequence, strand, cigar):
|
|
153
|
+
"""
|
|
154
|
+
根据 strand 和 cigar 信息提取指定长度的序列部分。
|
|
155
|
+
为direct RNA设置
|
|
156
|
+
参数:
|
|
157
|
+
- sequence (str): 原始的序列字符串。这个sequence已经被reverse处理过了,与正链一致,所以取序列的时候都从开头取起
|
|
158
|
+
- strand (str): 链方向,"+" 或 "-"。
|
|
159
|
+
- cigar (list of tuples): CIGAR 信息,表示为列表的元组 (operation, length)。
|
|
160
|
+
例如 [(4, 5), (0, 50)] 表示 5 bp 的 soft clipping,接着是 50 bp 的匹配。
|
|
161
|
+
返回:
|
|
162
|
+
- str: 根据条件截取后的序列。再多加两个碱基在后面
|
|
163
|
+
"""
|
|
164
|
+
if not cigar:
|
|
165
|
+
return "" # Return empty string if no CIGAR info
|
|
166
|
+
|
|
167
|
+
if strand == "+":
|
|
168
|
+
if cigar[0][0] == 4:
|
|
169
|
+
soft_clip_length = cigar[0][1]
|
|
170
|
+
# 返回从序列开头截取 soft_clip_length 长度的序列部分
|
|
171
|
+
return sequence[:soft_clip_length + 2] #
|
|
172
|
+
elif strand == "-":
|
|
173
|
+
if cigar[-1][0] == 4:
|
|
174
|
+
soft_clip_length = cigar[-1][1]
|
|
175
|
+
# 返回从序列开头截取 soft_clip_length 长度的序列部分
|
|
176
|
+
return sequence[:soft_clip_length + 2] #
|
|
177
|
+
else:
|
|
178
|
+
# 不符合条件时,返回原始序列
|
|
179
|
+
return ""
|
|
180
|
+
|
|
181
|
+
def soft_extract(sequence, cigar):
|
|
182
|
+
"""
|
|
183
|
+
根据 strand 和 cigar 信息提取指定长度的序列部分。
|
|
184
|
+
取两头,为双链cDNA而设置
|
|
185
|
+
参数:
|
|
186
|
+
- sequence (str): 原始的序列字符串
|
|
187
|
+
- cigar (list of tuples): CIGAR 信息,表示为列表的元组 (operation, length)。
|
|
188
|
+
例如 [(4, 5), (0, 50)] 表示 5 bp 的 soft clipping,接着是 50 bp 的匹配。
|
|
189
|
+
返回:
|
|
190
|
+
- str: 根据条件截取后的序列。再多加两个碱基在后面
|
|
191
|
+
"""
|
|
192
|
+
seq_5 = []
|
|
193
|
+
query_re_3 = []
|
|
194
|
+
if cigar[0][0] == 4:
|
|
195
|
+
soft_clip_length = cigar[0][1]
|
|
196
|
+
# 返回从序列开头截取 soft_clip_length 长度的序列部分
|
|
197
|
+
seq_5 = sequence[:soft_clip_length + 2] #
|
|
198
|
+
|
|
199
|
+
if cigar[-1][0] == 4:
|
|
200
|
+
soft_clip_length = cigar[-1][1]
|
|
201
|
+
# 返回从序列尾部截取 soft_clip_length 长度的序列部分
|
|
202
|
+
seq_3 = sequence[-(soft_clip_length + 2):] #
|
|
203
|
+
query_re_3 = str(Seq(seq_3).reverse_complement())
|
|
204
|
+
|
|
205
|
+
return seq_5, query_re_3
|
|
206
|
+
|
|
207
|
+
def ssw_wrapper(seq1, seq2, match=1, mismatch=1, gap_open=1, gap_extend=1):
|
|
208
|
+
"""
|
|
209
|
+
parameter are write inside the function
|
|
210
|
+
seq1 is ref and seq2 is query
|
|
211
|
+
# todo : leave a api to change matrix
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
ref_seq = str(seq1)
|
|
215
|
+
read_seq = str(seq2)
|
|
216
|
+
# reduce the gap open score from 3 to 1 for nanopore reads
|
|
217
|
+
aligner = Aligner(ref_seq,
|
|
218
|
+
match, mismatch, gap_open, gap_extend,
|
|
219
|
+
report_cigar=True, )
|
|
220
|
+
|
|
221
|
+
aln = aligner.align(read_seq) # min_score=20, min_len=10)
|
|
222
|
+
|
|
223
|
+
return aln
|
|
224
|
+
|
|
225
|
+
def consensus(ref, query, cigar_string, ref_shift=0, query_shift = 0):
|
|
226
|
+
"""
|
|
227
|
+
to get a concensus sequence as the longest cons of both sequence,
|
|
228
|
+
and fill all disagreement with N
|
|
229
|
+
for example,
|
|
230
|
+
query:AAATA-TAGAA
|
|
231
|
+
ref: AAACACTA-AA
|
|
232
|
+
cons: AAANANCANAA
|
|
233
|
+
|
|
234
|
+
use query to get the con
|
|
235
|
+
then get two chain file, 0 based
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
# pattern = re.compile('([0-9]*)([MIDNSHP=X])')
|
|
239
|
+
pattern = re.compile(r"([0-9]+)b?'?([MIDNSHP=X])")
|
|
240
|
+
query_out = []
|
|
241
|
+
ref_out = []
|
|
242
|
+
cons = []
|
|
243
|
+
|
|
244
|
+
query_pos = query_shift
|
|
245
|
+
ref_pos = ref_shift
|
|
246
|
+
|
|
247
|
+
cigar_pattern = pattern.findall(cigar_string)
|
|
248
|
+
for i, (length, code) in enumerate(cigar_pattern):
|
|
249
|
+
if i == 0 or i == len(cigar_pattern) - 1:
|
|
250
|
+
if code == "S":
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
length = int(length)
|
|
254
|
+
|
|
255
|
+
if code == "M":
|
|
256
|
+
for i in range(length):
|
|
257
|
+
q_s = query[query_pos].upper()
|
|
258
|
+
try:
|
|
259
|
+
r_s = ref[ref_pos].upper()
|
|
260
|
+
except IndexError as e:
|
|
261
|
+
print
|
|
262
|
+
"indexerror", ref_pos
|
|
263
|
+
|
|
264
|
+
query_out.append(q_s)
|
|
265
|
+
ref_out.append(r_s)
|
|
266
|
+
|
|
267
|
+
cons.append(q_s) if q_s == r_s else cons.append("m")
|
|
268
|
+
|
|
269
|
+
ref_pos += 1
|
|
270
|
+
query_pos += 1
|
|
271
|
+
|
|
272
|
+
elif code in "D":
|
|
273
|
+
for i in range(length):
|
|
274
|
+
r_s = ref[ref_pos]
|
|
275
|
+
|
|
276
|
+
ref_out.append(r_s)
|
|
277
|
+
query_out.append("-")
|
|
278
|
+
cons.append("d")
|
|
279
|
+
|
|
280
|
+
ref_pos += 1
|
|
281
|
+
|
|
282
|
+
elif code in "IHS":
|
|
283
|
+
for i in range(length):
|
|
284
|
+
q_s = query[query_pos]
|
|
285
|
+
|
|
286
|
+
query_out.append(q_s)
|
|
287
|
+
ref_out.append("-")
|
|
288
|
+
cons.append("i")
|
|
289
|
+
query_pos += 1
|
|
290
|
+
|
|
291
|
+
return "".join(cons) # "".join(cons), "".join(query_out), "".join(ref_out)
|
|
292
|
+
|
|
293
|
+
def drs_score_calculate(sw_score, max_intersection, max_consecutive, ref_end, ref_length, random_seq_len, seq_start, soft_length):
|
|
294
|
+
# factor = get_value(best_end, ref_length - k)
|
|
295
|
+
|
|
296
|
+
score_seq = (1 - seq_start/soft_length) # * (seq_length/soft_length) # 在soft clipping中的位置,越靠头部越好
|
|
297
|
+
# score_ref = 1 - (ref_length - ref_end)/ref_length
|
|
298
|
+
score_ref = 1 - (ref_length - ref_end)/(random_seq_len + 3) #(SL1_len - k)*2
|
|
299
|
+
score_kmer = 0.5 * (max_intersection + max_consecutive - 1)
|
|
300
|
+
score = score_seq * score_ref * (sw_score + score_kmer)
|
|
301
|
+
|
|
302
|
+
final_score = max(score, 0)
|
|
303
|
+
return final_score
|
|
304
|
+
|
|
305
|
+
def cdna_score_calculate(sw_score, max_intersection, max_consecutive, ref_end, ref_length, random_seq_len, seq_end, soft_length):
|
|
306
|
+
# factor = get_value(best_end, ref_length - k)
|
|
307
|
+
|
|
308
|
+
score_seq = seq_end/soft_length # * (seq_length/soft_length) # 在soft clipping中的位置,越靠alignment region越好
|
|
309
|
+
# score_ref = 1 - (ref_length - ref_end)/ref_length
|
|
310
|
+
score_ref = 1 - (ref_length - ref_end)/(random_seq_len + 3) #(SL1_len - k)*2
|
|
311
|
+
score_kmer = 0.5 * (max_intersection + max_consecutive - 1)
|
|
312
|
+
score = score_seq * score_ref * (sw_score + score_kmer)
|
|
313
|
+
|
|
314
|
+
final_score = max(score, 0)
|
|
315
|
+
return final_score
|
|
316
|
+
|
|
317
|
+
def ref_score_calculate(sw_score, max_intersection, max_consecutive):
|
|
318
|
+
score_kmer = 0.5 * (max_intersection + max_consecutive - 1)
|
|
319
|
+
score = (sw_score + score_kmer)
|
|
320
|
+
|
|
321
|
+
final_score = max(score, 0)
|
|
322
|
+
return final_score
|
|
323
|
+
|
|
324
|
+
def length_index(SL, ref_seq, kmer, mismatch_to_kmer, random_seq_len, k):
|
|
325
|
+
dict = {}
|
|
326
|
+
|
|
327
|
+
for i in range(k, len(ref_seq) + 1):
|
|
328
|
+
ref_seq_s = ref_seq[-i:]
|
|
329
|
+
sw_aln = ssw_wrapper(ref_seq, ref_seq_s) # score>10,
|
|
330
|
+
sw_score = sw_aln.score
|
|
331
|
+
# ref_start = sw_aln.ref_begin
|
|
332
|
+
# ref_end = sw_aln.ref_end
|
|
333
|
+
# read_start = sw_aln.query_begin
|
|
334
|
+
read_end = sw_aln.query_end + 1
|
|
335
|
+
# sw_cigar = sw_aln.cigar_string
|
|
336
|
+
|
|
337
|
+
corrected_sequence_sw = ref_seq_s
|
|
338
|
+
# last_three_chars = corrected_sequence_sw[-3:]
|
|
339
|
+
seq_s_length = len(corrected_sequence_sw)
|
|
340
|
+
ref_length = len(ref_seq)
|
|
341
|
+
# seq_length = read_end - read_start
|
|
342
|
+
kmer_to_refs = kmer[SL]
|
|
343
|
+
max_intersection, max_consecutive, best_end = find_best_match(corrected_sequence_sw, mismatch_to_kmer,
|
|
344
|
+
kmer_to_refs, k)
|
|
345
|
+
final_score = ref_score_calculate(sw_score, max_intersection, max_consecutive)
|
|
346
|
+
dict[i] = final_score
|
|
347
|
+
|
|
348
|
+
return dict
|
|
349
|
+
|
|
350
|
+
def final_score_process(final_score, ref_length, seq_s_length, length_score):
|
|
351
|
+
if seq_s_length <= ref_length:
|
|
352
|
+
SL_score = final_score * (final_score / length_score[seq_s_length])
|
|
353
|
+
# final_score_normalized = 100 * (final_score / length_score[seq_s_length])
|
|
354
|
+
else:
|
|
355
|
+
SL_score = final_score * (final_score / (seq_s_length / ref_length * length_score[ref_length]))
|
|
356
|
+
# final_score_normalized = 100 * (final_score / (seq_s_length / ref_length * length_score[ref_length]))
|
|
357
|
+
return SL_score # final_score_normalized
|
|
358
|
+
|
|
359
|
+
def random_score(random_sequences_dict, random_kmer, random_mismatch_to_kmer,length_scores, random_seq_len, corrected_sequence, k):
|
|
360
|
+
random_sw_score_max = 0
|
|
361
|
+
random_final_score_max = 0
|
|
362
|
+
random_SL_score_max = 0
|
|
363
|
+
length_score = length_scores[random_seq_len]
|
|
364
|
+
for key, random_seq in random_sequences_dict.items():
|
|
365
|
+
random_length_score = length_score
|
|
366
|
+
random_aln_sw = ssw_wrapper(random_seq, corrected_sequence)
|
|
367
|
+
random_sw_score = random_aln_sw.score
|
|
368
|
+
random_read_start = random_aln_sw.query_begin
|
|
369
|
+
random_read_end = random_aln_sw.query_end + 1
|
|
370
|
+
corrected_sequence_sw = corrected_sequence[random_read_start:random_read_end]
|
|
371
|
+
seq_s_length = len(corrected_sequence_sw)
|
|
372
|
+
|
|
373
|
+
if seq_s_length >= 5:
|
|
374
|
+
random_ref_end = random_aln_sw.ref_end + 1
|
|
375
|
+
random_ref_length = len(random_seq)
|
|
376
|
+
kmer_to_refs = random_kmer[key]
|
|
377
|
+
max_intersection, max_consecutive, best_end = find_best_match(corrected_sequence_sw,
|
|
378
|
+
random_mismatch_to_kmer,
|
|
379
|
+
kmer_to_refs, k)
|
|
380
|
+
if mode == 'RNA':
|
|
381
|
+
random_final_score = drs_score_calculate(random_sw_score, max_intersection, max_consecutive, random_ref_end,
|
|
382
|
+
random_ref_length, random_seq_len, random_read_start, len(corrected_sequence))
|
|
383
|
+
else:
|
|
384
|
+
random_final_score = cdna_score_calculate(random_sw_score, max_intersection, max_consecutive, random_ref_end,
|
|
385
|
+
random_ref_length, random_seq_len, random_read_end, len(corrected_sequence))
|
|
386
|
+
random_SL_score = final_score_process(random_final_score, random_ref_length,
|
|
387
|
+
seq_s_length, random_length_score)
|
|
388
|
+
if random_SL_score > random_SL_score_max:
|
|
389
|
+
random_sw_score_max = random_sw_score
|
|
390
|
+
random_final_score_max = random_final_score
|
|
391
|
+
random_SL_score_max = random_SL_score
|
|
392
|
+
|
|
393
|
+
return random_sw_score_max, random_final_score_max, random_SL_score_max
|
|
394
|
+
|
|
395
|
+
def update(*a):
|
|
396
|
+
if a[0] is not None:
|
|
397
|
+
outfile.write(a[0])
|
|
398
|
+
pbar.update(1)
|
|
399
|
+
|
|
400
|
+
def drs_calculation_per_process(item,sl_dict,length_scores,random_sequences_dict,random_seq_len,random_kmer,
|
|
401
|
+
random_mismatch_to_kmer,k,kmer,mismatch_to_kmer):
|
|
402
|
+
query_name = item[0]
|
|
403
|
+
full_query_sequence = item[1]
|
|
404
|
+
strand = item[2]
|
|
405
|
+
if strand == '-':
|
|
406
|
+
query_seq = str(Seq(full_query_sequence).reverse_complement()) # 序列映射到负链,需要反向互补处理
|
|
407
|
+
else:
|
|
408
|
+
query_seq = full_query_sequence # 序列映射到正链,直接使用
|
|
409
|
+
|
|
410
|
+
corrected_sequence = soft_processed(query_seq, strand, item[3])
|
|
411
|
+
aligned_len = item[4]
|
|
412
|
+
|
|
413
|
+
if corrected_sequence is not None:
|
|
414
|
+
soft_length = len(corrected_sequence)
|
|
415
|
+
mes = query_name + '\t' + strand + '\t' + str(soft_length) + '\t' + str(aligned_len) + '\t' # with soft_processed
|
|
416
|
+
else:
|
|
417
|
+
soft_length = 0
|
|
418
|
+
mes = query_name + '\t' + strand + '\t' + 'NA' + '\t' + str(aligned_len) + '\t' # with soft_processed
|
|
419
|
+
|
|
420
|
+
if soft_length < k: # 太短了就不要了
|
|
421
|
+
SL_sw_dict = {'read_end': 'NA', 'query_length': 'NA',
|
|
422
|
+
'consensus': 'NA', 'random_sw_score': 'NA',
|
|
423
|
+
'random_final_score': 'NA', 'random_SL_score': 'NA',
|
|
424
|
+
'sw_score': 'NA', 'final_score': 'NA',
|
|
425
|
+
'SL_score': 'NA'}
|
|
426
|
+
mes = mes + '\t'.join([str(value) for value in SL_sw_dict.values()]) + '\t' + 'random' + "\n"
|
|
427
|
+
return mes
|
|
428
|
+
|
|
429
|
+
### use SL1 seq for SW check
|
|
430
|
+
SL_sw_dict = {}
|
|
431
|
+
for SL, SEQ in sl_dict.items():
|
|
432
|
+
length_score = length_scores[len(SEQ)]
|
|
433
|
+
# corrected_sequence = 'CAAG'
|
|
434
|
+
sw_aln = ssw_wrapper(SEQ, corrected_sequence) # score>10,
|
|
435
|
+
sw_score = sw_aln.score
|
|
436
|
+
ref_start = sw_aln.ref_begin
|
|
437
|
+
ref_end = sw_aln.ref_end + 1
|
|
438
|
+
read_start = sw_aln.query_begin
|
|
439
|
+
read_end = sw_aln.query_end + 1
|
|
440
|
+
sw_cigar = sw_aln.cigar_string
|
|
441
|
+
|
|
442
|
+
corrected_sequence_sw = corrected_sequence[read_start:read_end]
|
|
443
|
+
|
|
444
|
+
seq_s_length = len(corrected_sequence_sw)
|
|
445
|
+
random_sw_score_max, random_final_score_max, random_SL_score_max = random_score(random_sequences_dict,
|
|
446
|
+
random_kmer,
|
|
447
|
+
random_mismatch_to_kmer,
|
|
448
|
+
length_scores, random_seq_len,
|
|
449
|
+
corrected_sequence, k)
|
|
450
|
+
if seq_s_length >= k:
|
|
451
|
+
sw_ref = SEQ[ref_start:ref_end]
|
|
452
|
+
|
|
453
|
+
# print("\n".join(consensus(corrected_sequence_sw, sw_ref, sw_cigar, 0)))
|
|
454
|
+
# last_three_chars = corrected_sequence_sw[-3:]
|
|
455
|
+
con_seq = consensus(sw_ref, corrected_sequence_sw, sw_cigar, 0)
|
|
456
|
+
ref_length = len(SEQ)
|
|
457
|
+
kmer_to_refs = kmer[SL]
|
|
458
|
+
max_intersection, max_consecutive, best_end = find_best_match(corrected_sequence_sw, mismatch_to_kmer,
|
|
459
|
+
kmer_to_refs, k)
|
|
460
|
+
final_score = drs_score_calculate(sw_score, max_intersection, max_consecutive, ref_end, ref_length,
|
|
461
|
+
random_seq_len, read_start, soft_length) # sw_score, max_intersection, max_consecutive, ref_end, ref_length, seq_start, seq_length, soft_length
|
|
462
|
+
SL_score = final_score_process(final_score, ref_length, seq_s_length, length_score)
|
|
463
|
+
|
|
464
|
+
SL_sw_dict[SL] = {'read_end': read_end,
|
|
465
|
+
'query_length': seq_s_length,
|
|
466
|
+
'consensus': con_seq,
|
|
467
|
+
'random_sw_score': random_sw_score_max,
|
|
468
|
+
'random_final_score': round(random_final_score_max, 2),
|
|
469
|
+
'random_SL_score': round(random_SL_score_max, 2),
|
|
470
|
+
'sw_score': sw_score,
|
|
471
|
+
'final_score': round(final_score, 2),
|
|
472
|
+
'SL_score': round(SL_score, 2)
|
|
473
|
+
}
|
|
474
|
+
else:
|
|
475
|
+
SL_sw_dict[SL] = {'read_end': read_end,
|
|
476
|
+
'query_length': None,
|
|
477
|
+
'consensus': None,
|
|
478
|
+
'random_sw_score': 0,
|
|
479
|
+
'random_final_score': 0,
|
|
480
|
+
'random_SL_score': 0,
|
|
481
|
+
'sw_score': 0,
|
|
482
|
+
'final_score': 0,
|
|
483
|
+
'SL_score': 0
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
SL_sw_df = pd.DataFrame.from_dict(SL_sw_dict, orient='index')
|
|
487
|
+
SL_sw_df_s = SL_sw_df[SL_sw_df['SL_score'] > SL_sw_df['random_SL_score']]
|
|
488
|
+
sl_max = SL_sw_df_s['SL_score'].max()
|
|
489
|
+
# filtered_df = SL_sw_df_s[SL_sw_df_s['SL_score'] == sl_max]
|
|
490
|
+
filtered_df = SL_sw_df_s[SL_sw_df_s['SL_score'] == sl_max]
|
|
491
|
+
|
|
492
|
+
if len(filtered_df) > 1:
|
|
493
|
+
mes = mes + '\t'.join([str(value) for value in filtered_df.iloc[0]]) + '\t' + filtered_df.index[
|
|
494
|
+
0] + '_unknown' + "\n"
|
|
495
|
+
elif len(filtered_df) == 1:
|
|
496
|
+
mes = mes + '\t'.join([str(value) for value in filtered_df.iloc[0]]) + '\t' + filtered_df.index[0] + "\n"
|
|
497
|
+
else:
|
|
498
|
+
filtered_df_s = SL_sw_df[SL_sw_df['SL_score'] == SL_sw_df['SL_score'].max()]
|
|
499
|
+
mes = mes + '\t'.join([str(value) for value in filtered_df_s.iloc[0]]) + '\t' + 'random' + "\n"
|
|
500
|
+
return mes
|
|
501
|
+
|
|
502
|
+
def cdna_calculation_per_process(item,sl_dict,length_scores,random_sequences_dict,random_seq_len,random_kmer,
|
|
503
|
+
random_mismatch_to_kmer,k,kmer,mismatch_to_kmer):
|
|
504
|
+
query_name = item[0]
|
|
505
|
+
query_seq = item[1]
|
|
506
|
+
strand = item[2]
|
|
507
|
+
aligned_len = item[4]
|
|
508
|
+
seq_5, seq_3 = soft_extract(query_seq, item[3])
|
|
509
|
+
candidate_seq = [seq_5, seq_3]
|
|
510
|
+
|
|
511
|
+
if seq_5 is None and seq_3 is None:
|
|
512
|
+
mes = query_name + '\t' + strand + '\t' + 'NA' + str(aligned_len) + '\t' # with soft_processed
|
|
513
|
+
SL_sw_dict = {'read_end': 'NA', 'query_length': 'NA',
|
|
514
|
+
'consensus': 'NA', 'random_sw_score': 'NA',
|
|
515
|
+
'random_final_score': 'NA', 'random_SL_score': 'NA',
|
|
516
|
+
'sw_score': 'NA', 'final_score': 'NA',
|
|
517
|
+
'SL_score': 'NA'}
|
|
518
|
+
mes = mes + '\t'.join([str(value) for value in SL_sw_dict.values()]) + '\t' + 'random' + "\n"
|
|
519
|
+
return mes
|
|
520
|
+
elif len(seq_5) < 5 and len(seq_3) < 5:
|
|
521
|
+
soft_length = max(len(seq_5), len(seq_3))
|
|
522
|
+
mes = query_name + '\t' + strand + '\t' + str(soft_length) + str(aligned_len) + '\t' # with soft_processed
|
|
523
|
+
SL_sw_dict = {'read_end': 'NA', 'query_length': 'NA',
|
|
524
|
+
'consensus': 'NA', 'random_sw_score': 'NA',
|
|
525
|
+
'random_final_score': 'NA', 'random_SL_score': 'NA',
|
|
526
|
+
'sw_score': 'NA', 'final_score': 'NA',
|
|
527
|
+
'SL_score': 'NA'}
|
|
528
|
+
mes = mes + '\t'.join([str(value) for value in SL_sw_dict.values()]) + '\t' + 'random' + "\n"
|
|
529
|
+
return mes
|
|
530
|
+
|
|
531
|
+
results = []
|
|
532
|
+
for corrected_sequence in candidate_seq:
|
|
533
|
+
soft_length = len(corrected_sequence)
|
|
534
|
+
### use SL1 seq for SW check
|
|
535
|
+
SL_sw_dict = {}
|
|
536
|
+
for SL, SEQ in sl_dict.items():
|
|
537
|
+
length_score = length_scores[len(SEQ)]
|
|
538
|
+
# corrected_sequence = 'CAAG'
|
|
539
|
+
sw_aln = ssw_wrapper(SEQ, corrected_sequence) # score>10,
|
|
540
|
+
sw_score = sw_aln.score
|
|
541
|
+
ref_start = sw_aln.ref_begin
|
|
542
|
+
ref_end = sw_aln.ref_end
|
|
543
|
+
read_start = sw_aln.query_begin
|
|
544
|
+
read_end = sw_aln.query_end + 1
|
|
545
|
+
sw_cigar = sw_aln.cigar_string
|
|
546
|
+
|
|
547
|
+
corrected_sequence_sw = corrected_sequence[read_start:read_end]
|
|
548
|
+
|
|
549
|
+
seq_s_length = len(corrected_sequence_sw)
|
|
550
|
+
random_sw_score_max, random_final_score_max, random_SL_score_max = random_score(random_sequences_dict,
|
|
551
|
+
random_kmer,
|
|
552
|
+
random_mismatch_to_kmer,
|
|
553
|
+
length_scores,
|
|
554
|
+
random_seq_len,
|
|
555
|
+
corrected_sequence, k)
|
|
556
|
+
if seq_s_length >= k:
|
|
557
|
+
sw_ref = SEQ[ref_start:ref_end]
|
|
558
|
+
|
|
559
|
+
# print("\n".join(consensus(corrected_sequence_sw, sw_ref, sw_cigar, 0)))
|
|
560
|
+
# last_three_chars = corrected_sequence_sw[-3:]
|
|
561
|
+
con_seq = consensus(sw_ref, corrected_sequence_sw, sw_cigar, 0)
|
|
562
|
+
ref_length = len(SEQ)
|
|
563
|
+
kmer_to_refs = kmer[SL]
|
|
564
|
+
max_intersection, max_consecutive, best_end = find_best_match(corrected_sequence_sw, mismatch_to_kmer,
|
|
565
|
+
kmer_to_refs, k)
|
|
566
|
+
final_score = cdna_score_calculate(sw_score, max_intersection, max_consecutive, ref_end, ref_length,
|
|
567
|
+
random_seq_len, read_end, soft_length) # sw_score, max_intersection, max_consecutive, ref_end, ref_length, seq_start, seq_length, soft_length
|
|
568
|
+
SL_score = final_score_process(final_score, ref_length, seq_s_length, length_score)
|
|
569
|
+
|
|
570
|
+
SL_sw_dict[SL] = {'read_end': read_end,
|
|
571
|
+
'query_length': seq_s_length,
|
|
572
|
+
'consensus': con_seq,
|
|
573
|
+
'random_sw_score': random_sw_score_max,
|
|
574
|
+
'random_final_score': round(random_final_score_max, 2),
|
|
575
|
+
'random_SL_score': round(random_SL_score_max, 2),
|
|
576
|
+
'sw_score': sw_score,
|
|
577
|
+
'final_score': round(final_score, 2),
|
|
578
|
+
'SL_score': round(SL_score, 2)
|
|
579
|
+
}
|
|
580
|
+
else:
|
|
581
|
+
SL_sw_dict[SL] = {'read_end': read_end,
|
|
582
|
+
'query_length': None,
|
|
583
|
+
'consensus': None,
|
|
584
|
+
'random_sw_score': 0,
|
|
585
|
+
'random_final_score': 0,
|
|
586
|
+
'random_SL_score': 0,
|
|
587
|
+
'sw_score': 0,
|
|
588
|
+
'final_score': 0,
|
|
589
|
+
'SL_score': 0
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
SL_sw_df = pd.DataFrame.from_dict(SL_sw_dict, orient='index')
|
|
593
|
+
SL_sw_df_s = SL_sw_df[SL_sw_df['SL_score'] > SL_sw_df['random_SL_score']]
|
|
594
|
+
sl_max = 0 if pd.isna(SL_sw_df_s['SL_score'].max()) else SL_sw_df_s['SL_score'].max()
|
|
595
|
+
sl_max_f = SL_sw_df['SL_score'].max()
|
|
596
|
+
filtered_df = SL_sw_df_s[SL_sw_df_s['SL_score'] == sl_max]
|
|
597
|
+
filtered_df_f = SL_sw_df[SL_sw_df['SL_score'] == sl_max_f]
|
|
598
|
+
results.append({
|
|
599
|
+
"soft_length": soft_length,
|
|
600
|
+
"sl_max": sl_max,
|
|
601
|
+
"sl_max_f": sl_max_f,
|
|
602
|
+
"filtered_df": filtered_df,
|
|
603
|
+
"filtered_df_f": filtered_df_f
|
|
604
|
+
})
|
|
605
|
+
best_filtered_df = []
|
|
606
|
+
best_filtered_df_f = []
|
|
607
|
+
if len(results) == 1:
|
|
608
|
+
# 只有一个非空序列,直接使用其 filtered_df
|
|
609
|
+
soft_length = results[0]['soft_length']
|
|
610
|
+
best_filtered_df = results[0]["filtered_df"]
|
|
611
|
+
elif len(results) == 2:
|
|
612
|
+
seq1, seq2 = results
|
|
613
|
+
# 情况 1: 至少有一个 sl_max > 0,选择 sl_max 较大的
|
|
614
|
+
if seq1["sl_max"] > 0 or seq2["sl_max"] > 0:
|
|
615
|
+
if seq1["sl_max"] > seq2["sl_max"]:
|
|
616
|
+
best_filtered_df = seq1["filtered_df"]
|
|
617
|
+
soft_length = seq1["soft_length"]
|
|
618
|
+
else:
|
|
619
|
+
best_filtered_df = seq2["filtered_df"]
|
|
620
|
+
soft_length = seq2["soft_length"]
|
|
621
|
+
# 情况 2: 两个 sl_max <= 0,比较 sl_max_f,选择 sl_max_f 较小的
|
|
622
|
+
else:
|
|
623
|
+
if seq1["sl_max_f"] >= seq2["sl_max_f"]:
|
|
624
|
+
best_filtered_df_f = seq1["filtered_df_f"]
|
|
625
|
+
soft_length = seq1["soft_length"]
|
|
626
|
+
else:
|
|
627
|
+
best_filtered_df_f = seq2["filtered_df_f"]
|
|
628
|
+
soft_length = seq2["soft_length"]
|
|
629
|
+
|
|
630
|
+
mes = query_name + '\t' + strand + '\t' + str(soft_length) + '\t' + str(aligned_len) + '\t' # with soft_processed
|
|
631
|
+
if len(best_filtered_df) > 1:
|
|
632
|
+
mes = mes + '\t'.join([str(value) for value in best_filtered_df.iloc[0]]) + '\t' + best_filtered_df.index[
|
|
633
|
+
0] + '_unknown' + "\n"
|
|
634
|
+
elif len(best_filtered_df) == 1:
|
|
635
|
+
mes = mes + '\t'.join([str(value) for value in best_filtered_df.iloc[0]]) + '\t' + best_filtered_df.index[
|
|
636
|
+
0] + "\n"
|
|
637
|
+
else:
|
|
638
|
+
mes = mes + '\t'.join([str(value) for value in best_filtered_df_f.iloc[0]]) + '\t' + 'random' + "\n"
|
|
639
|
+
|
|
640
|
+
return mes
|
|
641
|
+
|
|
642
|
+
def main(args):
|
|
643
|
+
global outfile, pbar, mode
|
|
644
|
+
"""
|
|
645
|
+
SW comparison between SL1 and SL2
|
|
646
|
+
read reads in bam
|
|
647
|
+
write out a dataframe that including:
|
|
648
|
+
query name, 22nt sequence, SW score, SL1 score, SL2 score, SL1 cigar, SL2 cigar ,SL type
|
|
649
|
+
query name, selected 22nt sequence with soft clipping...
|
|
650
|
+
"""
|
|
651
|
+
mode = args.mode
|
|
652
|
+
sl_dict = fasta_to_dict(args.refer)
|
|
653
|
+
|
|
654
|
+
# 生成10个长度为SL1长度的碱基的随机序列
|
|
655
|
+
ref_lengths = [len(key) for key in sl_dict.values()]
|
|
656
|
+
random_seq_len = round(sum(ref_lengths) / len(ref_lengths))
|
|
657
|
+
random.seed(826)
|
|
658
|
+
random_sequences_dict = {}
|
|
659
|
+
for i in range(10):
|
|
660
|
+
random_sequence = ''.join([random.choice('AGTC') for _ in range(random_seq_len)])
|
|
661
|
+
random_sequences_dict[i] = random_sequence
|
|
662
|
+
|
|
663
|
+
k = 5
|
|
664
|
+
kmer = extract_kmers(sl_dict, k)
|
|
665
|
+
mismatch_to_kmer = build_mismatch_index(sl_dict, k)
|
|
666
|
+
random_kmer = extract_kmers(random_sequences_dict, k)
|
|
667
|
+
random_mismatch_to_kmer = build_mismatch_index(random_sequences_dict, k)
|
|
668
|
+
|
|
669
|
+
length_scores = {}
|
|
670
|
+
|
|
671
|
+
SL_ref_length = get_sequences_by_length(sl_dict)
|
|
672
|
+
for SL, info in SL_ref_length.items():
|
|
673
|
+
length_score = length_index(SL, info['sequence'], kmer, mismatch_to_kmer, random_seq_len, k)
|
|
674
|
+
length_scores[len(info['sequence'])] = length_score
|
|
675
|
+
|
|
676
|
+
bam_file = pysam.AlignmentFile(args.input, 'rb')
|
|
677
|
+
timestamp = int(time.time())
|
|
678
|
+
tmp_output_name = f"tmp_{timestamp}.csv"
|
|
679
|
+
outfile = open(tmp_output_name, "w")
|
|
680
|
+
outfile.write("query_name\tstrand\tsoft_length\taligned_length\tread_end\tquery_length\tconsensus\trandom_sw_score\trandom_final_score\trandom_SL_score\tsw_score\tfinal_score\tSL_score\tSL_type\n")
|
|
681
|
+
|
|
682
|
+
# 迭代每个read
|
|
683
|
+
print('Loading the BAM file')
|
|
684
|
+
bam_list=[]
|
|
685
|
+
for read in bam_file.fetch():
|
|
686
|
+
if read.is_supplementary or read.is_secondary:
|
|
687
|
+
continue
|
|
688
|
+
### sequence extract
|
|
689
|
+
query_name = read.query_name # 获取query的名称
|
|
690
|
+
full_query_sequence = read.query_sequence # 获取query的序列
|
|
691
|
+
if read.is_reverse:
|
|
692
|
+
strand = '-'
|
|
693
|
+
else:
|
|
694
|
+
strand = '+'
|
|
695
|
+
bam_list.append([query_name,full_query_sequence,strand,read.cigartuples,read.query_alignment_length])
|
|
696
|
+
pbar = tqdm(total=len(bam_list), position=0, leave=True)
|
|
697
|
+
|
|
698
|
+
if mode == 'RNA':
|
|
699
|
+
with multiprocessing.Pool(processes=args.cpu) as pool:
|
|
700
|
+
for item in bam_list:
|
|
701
|
+
pool.apply_async(drs_calculation_per_process, args=(item, sl_dict, length_scores, random_sequences_dict,
|
|
702
|
+
random_seq_len, random_kmer, random_mismatch_to_kmer,
|
|
703
|
+
k, kmer, mismatch_to_kmer), callback=update)
|
|
704
|
+
pool.close()
|
|
705
|
+
pool.join()
|
|
706
|
+
elif mode == 'cDNA':
|
|
707
|
+
with multiprocessing.Pool(processes=args.cpu) as pool:
|
|
708
|
+
for item in bam_list:
|
|
709
|
+
pool.apply_async(cdna_calculation_per_process, args=(item, sl_dict, length_scores, random_sequences_dict,
|
|
710
|
+
random_seq_len, random_kmer,
|
|
711
|
+
random_mismatch_to_kmer,
|
|
712
|
+
k, kmer, mismatch_to_kmer), callback=update)
|
|
713
|
+
pool.close()
|
|
714
|
+
pool.join()
|
|
715
|
+
|
|
716
|
+
pbar.close()
|
|
717
|
+
outfile.close()
|
|
718
|
+
df = pd.read_csv(tmp_output_name,sep='\t')
|
|
719
|
+
df.sort_values(by=['query_name'], inplace=True)
|
|
720
|
+
df.to_csv(args.output, index=False,sep='\t')
|
|
721
|
+
if args.visualization:
|
|
722
|
+
visualize_html(args.output, args.cutoff)
|
|
723
|
+
print('Finished')
|
|
724
|
+
print('Finished')
|
|
725
|
+
|
|
726
|
+
if __name__ == '__main__':
|
|
727
|
+
parser = argparse.ArgumentParser(
|
|
728
|
+
description="help to know spliced leader and distinguish SL1 and SL2")
|
|
729
|
+
parser.add_argument("-r", "--refer", type=str,required=True,
|
|
730
|
+
help="SL reference")
|
|
731
|
+
parser.add_argument("-i", "--input", type=str, metavar="",required=True,
|
|
732
|
+
help="input the bam file")
|
|
733
|
+
parser.add_argument("-m", "--mode", type=str, metavar="", choices=['RNA','cDNA'],
|
|
734
|
+
default="RNA", help="RNA or cDNA")
|
|
735
|
+
parser.add_argument("-o", "--output", type=str, metavar="",
|
|
736
|
+
default="SLRanger_ppssw.txt",
|
|
737
|
+
help="output file")
|
|
738
|
+
parser.add_argument("-c", "--cutoff", type=float, default=4, help="cutoff of high confident SL sequence")
|
|
739
|
+
parser.add_argument("--visualization", action='store_true', help='Turn on the visualization mode')
|
|
740
|
+
parser.add_argument("-t", "--cpu", type=int,
|
|
741
|
+
default=1,
|
|
742
|
+
help="number if CPU")
|
|
743
|
+
args = parser.parse_args()
|
|
744
|
+
main(args)
|