FScanpy 1.0.0__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.
- FScanpy/__init__.py +206 -0
- FScanpy/data/__init__.py +122 -0
- FScanpy/data/test_data/blastx_example.xlsx +0 -0
- FScanpy/data/test_data/full_seq.xlsx +0 -0
- FScanpy/data/test_data/mrna_example.fasta +2584 -0
- FScanpy/data/test_data/region_example.csv +4 -0
- FScanpy/features/__init__.py +4 -0
- FScanpy/features/cnn_input.py +79 -0
- FScanpy/features/sequence.py +283 -0
- FScanpy/predictor.py +616 -0
- FScanpy/pretrained/long.pth +4 -0
- FScanpy/pretrained/short.pkl +0 -0
- FScanpy/utils.py +203 -0
- fscanpy-1.0.0.dist-info/METADATA +262 -0
- fscanpy-1.0.0.dist-info/RECORD +18 -0
- fscanpy-1.0.0.dist-info/WHEEL +5 -0
- fscanpy-1.0.0.dist-info/licenses/LICENSE +21 -0
- fscanpy-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
FS_period,399bp,fs_position,DNA_seqid,label,source,FS_type,dataset
|
|
2
|
+
gtgtgaacacaatagtgagtgacatactaaacg,ggatatgtaacatggacaagtcattgtgtaggtatccaagaccaatagcctttactttcaaagggaaagaagaattcaggaccttgtttggaggactcatctcgatgtcgattcaggtggtcattgtgctctatgcttatattatgctaaagataatgatagaacgtaatgacacatcaaaaagtgtgaacacaatagtgagtgacatactaaacgacaaatctccagtatctctcaatacaacagatttctcgtttgcatttgatgcttttattcttggcgatgataatttcgatttcaacaataaccaatacttcggaattgagctacttcaatggattaagcagccagatactggagaactatcatccactaatattccatatgaaagatgtggaa,16.0,MSTRG.18491.1,0,EUPLOTES,negative,EUPLOTES
|
|
3
|
+
gtctcagaagagtctgaggaatatctccaagga,caaattaataacaaatatgaattccatcaacaacttttatggagacgagaacttatcagatgaacttctgagtgaagatgtcgtgtcttgagaagtaagaggatcagaaaagatcttgcataacatggggagaaagtctctcagtaataagaagcctttaagcggagtggagttggactgagagtctcagaagagtctgaggaatatctccaaggataaaatttgttcgcaaggaagatctatctttaggcagaagaagtcaaaatcttgtgatcaagtagaagaacctcttagtagtcttaaagataacatgagtcactttaatgacatagacttgcaagctagtaagcctctaaaatcagagattagcaatctttttgggtactcaactcagcccaa,16.0,MSTRG.4662.1,0,EUPLOTES,negative,EUPLOTES
|
|
4
|
+
cttacttgcaaacatgaatctaataaattagag,ttaagaaggcataagagttttgctaaaaataaagatttgaagaatattactactaagtttggcaagagtaaacagagaagaagtaccatttctggctctccgacaaaatcagtcagatgcccttctgcaaaaaagagcctaacagatagaccaagaagaggaggtatccttgccaggaagaatcttacttgcaaacatgaatctaataaattagagatgctgatgaacctcatctatcgtacaccgaatgtagacctgattgaaaataggatcgatggactgataagaagtaactctatattgaacaaagtcgagaagagagtagctcactccggcattaagacttacaggttttctcctaatttactgaagaagataattccaaagaagataaaattc,16.0,MSTRG.14742.1,0,EUPLOTES,negative,EUPLOTES
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
|
|
4
|
+
class CNNInputProcessor:
|
|
5
|
+
"""CNN模型输入数据处理器"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, max_length: int = 399):
|
|
8
|
+
self.max_length = max_length
|
|
9
|
+
self.base_to_num = {'A': 0, 'T': 1, 'C': 2, 'G': 3, 'N': 4}
|
|
10
|
+
|
|
11
|
+
def trim_sequence(self, seq, target_length):
|
|
12
|
+
"""
|
|
13
|
+
从序列两端等量截取,使其达到目标长度,保持中心位置不变
|
|
14
|
+
|
|
15
|
+
参数:
|
|
16
|
+
seq: 原始序列
|
|
17
|
+
target_length: 目标长度
|
|
18
|
+
|
|
19
|
+
返回:
|
|
20
|
+
截取后的序列
|
|
21
|
+
"""
|
|
22
|
+
if len(seq) <= target_length:
|
|
23
|
+
return seq
|
|
24
|
+
|
|
25
|
+
# 计算需要从每端截取的长度
|
|
26
|
+
excess = len(seq) - target_length
|
|
27
|
+
trim_each_side = excess // 2
|
|
28
|
+
|
|
29
|
+
# 从两端等量截取,保持中心位置不变
|
|
30
|
+
return seq[trim_each_side:len(seq)-trim_each_side]
|
|
31
|
+
|
|
32
|
+
def prepare_sequence(self, sequence: str) -> np.ndarray:
|
|
33
|
+
"""
|
|
34
|
+
处理单个序列
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
sequence: DNA序列
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
np.ndarray: 处理后的序列数组
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# 序列验证和预处理
|
|
44
|
+
if not isinstance(sequence, str):
|
|
45
|
+
sequence = str(sequence)
|
|
46
|
+
|
|
47
|
+
sequence = sequence.upper().replace('U', 'T')
|
|
48
|
+
|
|
49
|
+
# 如果序列长度不等于目标长度,进行截取
|
|
50
|
+
if len(sequence) > self.max_length:
|
|
51
|
+
sequence = self.trim_sequence(sequence, self.max_length)
|
|
52
|
+
|
|
53
|
+
# 使用与训练时相同的编码方式
|
|
54
|
+
self.base_to_num = {'A': 0, 'T': 1, 'C': 2, 'G': 3, 'N': 4} # 与SemiBilstmCnn.py中保持一致
|
|
55
|
+
|
|
56
|
+
# 序列转换为数字
|
|
57
|
+
seq_numeric = []
|
|
58
|
+
for base in sequence:
|
|
59
|
+
seq_numeric.append(self.base_to_num.get(base, 4)) # 未知碱基用4表示
|
|
60
|
+
|
|
61
|
+
# 填充序列
|
|
62
|
+
if len(seq_numeric) < self.max_length:
|
|
63
|
+
seq_numeric.extend([4] * (self.max_length - len(seq_numeric)))
|
|
64
|
+
|
|
65
|
+
# 重塑数据为三维数组 (samples, timesteps, features)
|
|
66
|
+
result = np.array(seq_numeric).reshape(1, self.max_length, 1)
|
|
67
|
+
|
|
68
|
+
# 检查结果维度
|
|
69
|
+
if result.ndim != 3:
|
|
70
|
+
print(f"警告: CNN输入维度异常 - {result.ndim},应为3")
|
|
71
|
+
# 强制修正为正确的维度
|
|
72
|
+
result = result.reshape(1, self.max_length, 1)
|
|
73
|
+
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"CNN序列处理失败: {str(e)}")
|
|
78
|
+
# 出错时返回全零的三维数组
|
|
79
|
+
return np.zeros((1, self.max_length, 1))
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import itertools
|
|
4
|
+
from typing import List, Dict, Union
|
|
5
|
+
|
|
6
|
+
class SequenceFeatureExtractor:
|
|
7
|
+
"""DNA序列特征提取器"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, seq_length=33):
|
|
10
|
+
"""初始化特征提取器"""
|
|
11
|
+
self.bases = ['A', 'T', 'G', 'C']
|
|
12
|
+
self.valid_bases = set('ATGCN')
|
|
13
|
+
self.seq_length = seq_length # 添加序列长度配置
|
|
14
|
+
self.feature_names = self._get_feature_names()
|
|
15
|
+
|
|
16
|
+
def _get_feature_names(self) -> List[str]:
|
|
17
|
+
"""
|
|
18
|
+
返回特征名称列表,包含所有可能的碱基特征
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
features: 特征名称列表
|
|
22
|
+
"""
|
|
23
|
+
features = []
|
|
24
|
+
|
|
25
|
+
# 基础特征 (包含N)
|
|
26
|
+
bases = ['A', 'T', 'G', 'C', 'N']
|
|
27
|
+
features.extend(bases)
|
|
28
|
+
|
|
29
|
+
# 3-mer特征
|
|
30
|
+
kmers_3 = [''.join(p) for p in itertools.product(bases, repeat=3)] # 125个特征
|
|
31
|
+
features.extend(kmers_3)
|
|
32
|
+
|
|
33
|
+
# 密码子特征
|
|
34
|
+
codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)] # 64个密码子
|
|
35
|
+
n_codons = self.seq_length // 3 # 计算序列中包含的完整密码子数量
|
|
36
|
+
for i in range(n_codons):
|
|
37
|
+
for codon in codons:
|
|
38
|
+
features.append(f'codon_pos_{i}_{codon}')
|
|
39
|
+
|
|
40
|
+
# GC含量特征
|
|
41
|
+
features.append('gc_content')
|
|
42
|
+
|
|
43
|
+
# 序列复杂度特征
|
|
44
|
+
features.append('sequence_complexity')
|
|
45
|
+
|
|
46
|
+
return features
|
|
47
|
+
|
|
48
|
+
def trim_sequence(self, seq, target_length):
|
|
49
|
+
"""
|
|
50
|
+
从序列两端等量截取,使其达到目标长度
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
seq: 原始序列
|
|
54
|
+
target_length: 目标长度
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
截取后的序列
|
|
58
|
+
"""
|
|
59
|
+
if len(seq) <= target_length:
|
|
60
|
+
return seq
|
|
61
|
+
|
|
62
|
+
# 计算需要从每端截取的长度
|
|
63
|
+
excess = len(seq) - target_length
|
|
64
|
+
trim_each_side = excess // 2
|
|
65
|
+
|
|
66
|
+
# 从两端等量截取,保持中心位置不变
|
|
67
|
+
return seq[trim_each_side:len(seq)-trim_each_side]
|
|
68
|
+
|
|
69
|
+
def _preprocess_sequence(self, sequence):
|
|
70
|
+
"""
|
|
71
|
+
将DNA序列转换为特征向量
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
sequence: DNA序列
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
feature_vector: 特征向量
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
feature_names = self.feature_names
|
|
81
|
+
|
|
82
|
+
if pd.isna(sequence) or not isinstance(sequence, str):
|
|
83
|
+
sequence = str(sequence)
|
|
84
|
+
sequence = sequence.upper().replace('U', 'T') # 统一为大写字母
|
|
85
|
+
|
|
86
|
+
# 如果序列长度不等于目标长度,进行截取或填充
|
|
87
|
+
if len(sequence) > self.seq_length:
|
|
88
|
+
sequence = self.trim_sequence(sequence, self.seq_length)
|
|
89
|
+
else:
|
|
90
|
+
sequence = sequence[:self.seq_length].ljust(self.seq_length, 'N')
|
|
91
|
+
|
|
92
|
+
# 初始化特征字典
|
|
93
|
+
features = {
|
|
94
|
+
'A': 0,
|
|
95
|
+
'T': 0,
|
|
96
|
+
'G': 0,
|
|
97
|
+
'C': 0,
|
|
98
|
+
'N': 0
|
|
99
|
+
}
|
|
100
|
+
kmer_features = {}
|
|
101
|
+
|
|
102
|
+
# 碱基组成
|
|
103
|
+
for base in ['A', 'T', 'G', 'C', 'N']:
|
|
104
|
+
features[base] = sequence.count(base) / self.seq_length
|
|
105
|
+
|
|
106
|
+
# 3-mer特征
|
|
107
|
+
for kmer in [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C', 'N'], repeat=3)]:
|
|
108
|
+
kmer_count = 0
|
|
109
|
+
for i in range(self.seq_length - 2):
|
|
110
|
+
if sequence[i:i+3] == kmer:
|
|
111
|
+
kmer_count += 1
|
|
112
|
+
kmer_features[kmer] = kmer_count / max(1, self.seq_length - 2)
|
|
113
|
+
|
|
114
|
+
# 密码子特征
|
|
115
|
+
codon_features = {}
|
|
116
|
+
codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)] # 64个密码子
|
|
117
|
+
n_codons = self.seq_length // 3 # 计算序列中包含的完整密码子数量
|
|
118
|
+
for i in range(n_codons):
|
|
119
|
+
pos_start = i * 3
|
|
120
|
+
current_codon = sequence[pos_start:pos_start+3]
|
|
121
|
+
for codon in codons:
|
|
122
|
+
codon_features[f'codon_pos_{i}_{codon}'] = 1 if current_codon == codon and 'N' not in current_codon else 0
|
|
123
|
+
|
|
124
|
+
# GC含量
|
|
125
|
+
valid_bases = [b for b in sequence if b != 'N']
|
|
126
|
+
gc_content = (valid_bases.count('G') + valid_bases.count('C')) / len(valid_bases) if valid_bases else 0
|
|
127
|
+
|
|
128
|
+
# 序列复杂度(Shannon熵)
|
|
129
|
+
from collections import Counter
|
|
130
|
+
valid_counts = Counter(valid_bases)
|
|
131
|
+
total_valid = sum(valid_counts.values())
|
|
132
|
+
entropy = 0
|
|
133
|
+
for cnt in valid_counts.values():
|
|
134
|
+
p = cnt / total_valid
|
|
135
|
+
entropy += -p * np.log2(p)
|
|
136
|
+
entropy /= np.log2(4) # 归一化到0-1
|
|
137
|
+
|
|
138
|
+
# 合并所有特征
|
|
139
|
+
all_features = {**features, **kmer_features, **codon_features}
|
|
140
|
+
all_features['gc_content'] = gc_content
|
|
141
|
+
all_features['sequence_complexity'] = entropy
|
|
142
|
+
|
|
143
|
+
# 确保特征顺序一致
|
|
144
|
+
feature_vector = [all_features.get(f, 0.0) for f in feature_names]
|
|
145
|
+
|
|
146
|
+
return feature_vector
|
|
147
|
+
except Exception as e:
|
|
148
|
+
raise ValueError(f"特征提取失败: {str(e)}")
|
|
149
|
+
|
|
150
|
+
def extract_features_batch(self, sequences: List[Union[str, float]]) -> np.ndarray:
|
|
151
|
+
"""
|
|
152
|
+
批量提取特征
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
sequences: DNA序列列表
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
np.ndarray: 特征矩阵
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
return np.array([self.extract_features(seq) for seq in sequences])
|
|
162
|
+
except Exception as e:
|
|
163
|
+
raise ValueError(f"批量特征提取失败: {str(e)}")
|
|
164
|
+
|
|
165
|
+
def predict_region_batch(self, data: pd.DataFrame, gb_threshold: float = 0.1) -> pd.DataFrame:
|
|
166
|
+
"""
|
|
167
|
+
批量预测区域序列
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
data: DataFrame包含'33bp'和'399bp'列
|
|
171
|
+
gb_threshold: GB模型概率阈值(默认为0.1)
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
DataFrame: 包含预测结果的DataFrame
|
|
175
|
+
"""
|
|
176
|
+
results = []
|
|
177
|
+
for idx, row in data.iterrows():
|
|
178
|
+
try:
|
|
179
|
+
# 确保序列是字符串
|
|
180
|
+
seq_33bp = str(row['33bp'])
|
|
181
|
+
seq = str(row['399bp'])
|
|
182
|
+
|
|
183
|
+
# 确保序列长度正确
|
|
184
|
+
seq_33bp = self._preprocess_sequence(seq_33bp)
|
|
185
|
+
seq = self._preprocess_sequence(seq)
|
|
186
|
+
|
|
187
|
+
# 预测
|
|
188
|
+
result = self.predict_region(seq_33bp, seq, gb_threshold)
|
|
189
|
+
|
|
190
|
+
# 添加原始数据的其他列
|
|
191
|
+
for col in data.columns:
|
|
192
|
+
if col not in ['33bp', '399bp']:
|
|
193
|
+
result[col] = row[col]
|
|
194
|
+
|
|
195
|
+
results.append(result)
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print(f"处理索引 {idx} 的序列时出错: {str(e)}")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
return pd.DataFrame(results)
|
|
202
|
+
|
|
203
|
+
def extract_features(self, sequence: str) -> list:
|
|
204
|
+
"""
|
|
205
|
+
提取序列特征
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
sequence: DNA序列
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
list: 特征向量
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
# 确保输入是字符串
|
|
215
|
+
if not isinstance(sequence, str):
|
|
216
|
+
sequence = str(sequence)
|
|
217
|
+
|
|
218
|
+
# 大写并替换U为T
|
|
219
|
+
sequence = sequence.upper().replace('U', 'T')
|
|
220
|
+
|
|
221
|
+
# 如果序列长度不等于目标长度,进行截取
|
|
222
|
+
if len(sequence) != self.seq_length:
|
|
223
|
+
sequence = self.trim_sequence(sequence, self.seq_length)
|
|
224
|
+
|
|
225
|
+
# 初始化特征列表
|
|
226
|
+
features = []
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
# 基础特征 (碱基频率)
|
|
230
|
+
for base in ['A', 'T', 'G', 'C', 'N']:
|
|
231
|
+
features.append(sequence.count(base) / len(sequence))
|
|
232
|
+
|
|
233
|
+
# 3-mer特征
|
|
234
|
+
for kmer in [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C', 'N'], repeat=3)]:
|
|
235
|
+
count = 0
|
|
236
|
+
for i in range(len(sequence) - 2):
|
|
237
|
+
if sequence[i:i+3] == kmer:
|
|
238
|
+
count += 1
|
|
239
|
+
features.append(count / max(1, len(sequence) - 2))
|
|
240
|
+
|
|
241
|
+
# 密码子特征
|
|
242
|
+
codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)]
|
|
243
|
+
n_codons = len(sequence) // 3
|
|
244
|
+
for i in range(n_codons):
|
|
245
|
+
pos_start = i * 3
|
|
246
|
+
current_codon = sequence[pos_start:pos_start+3]
|
|
247
|
+
for codon in codons:
|
|
248
|
+
features.append(1 if current_codon == codon and 'N' not in current_codon else 0)
|
|
249
|
+
|
|
250
|
+
# GC含量
|
|
251
|
+
valid_bases = [b for b in sequence if b != 'N']
|
|
252
|
+
gc_content = (valid_bases.count('G') + valid_bases.count('C')) / len(valid_bases) if valid_bases else 0
|
|
253
|
+
features.append(gc_content)
|
|
254
|
+
|
|
255
|
+
# 序列复杂度
|
|
256
|
+
from collections import Counter
|
|
257
|
+
valid_counts = Counter(valid_bases)
|
|
258
|
+
total_valid = sum(valid_counts.values())
|
|
259
|
+
entropy = 0
|
|
260
|
+
if total_valid > 0: # 避免除零错误
|
|
261
|
+
for cnt in valid_counts.values():
|
|
262
|
+
if cnt > 0: # 避免log(0)
|
|
263
|
+
p = cnt / total_valid
|
|
264
|
+
entropy += -p * np.log2(p)
|
|
265
|
+
entropy /= np.log2(4) if len(valid_counts) > 0 else 1 # 归一化到0-1,避免除零
|
|
266
|
+
features.append(entropy)
|
|
267
|
+
|
|
268
|
+
# 确保返回的是一维列表或数组
|
|
269
|
+
if isinstance(features, np.ndarray) and features.ndim > 1:
|
|
270
|
+
features = features.flatten()
|
|
271
|
+
|
|
272
|
+
return features
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
print(f"特征计算过程出错: {str(e)}")
|
|
276
|
+
# 如果计算过程出错,返回正确长度的全零特征向量
|
|
277
|
+
expected_length = 5 + 125 + (len(sequence) // 3) * 64 + 2 # 根据特征提取逻辑计算特征向量长度
|
|
278
|
+
return [0.0] * expected_length
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
print(f"特征提取失败: {str(e)}")
|
|
282
|
+
# 返回一个空列表,调用方需处理这种情况
|
|
283
|
+
return []
|