File size: 10,322 Bytes
0da47ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
import os
import sys
import numpy as np
import pandas as pd
from Bio.PDB import PDBParser, PPBuilder, Superimposer
from Bio.PDB.Polypeptide import is_aa
from Bio import BiopythonWarning
import warnings
from tmtools import tm_align  # 导入tm_align函数

# 忽略 Biopython 的警告
warnings.simplefilter('ignore', BiopythonWarning)


def extract_sequence(pdb_path):
    """

    从 PDB 文件中提取第一个链的氨基酸序列。



    参数:

        pdb_path (str): PDB 文件的路径。



    返回:

        str: 氨基酸序列(单字母代码),如果无法提取则返回 None。

    """
    parser = PDBParser(QUIET=True)
    try:
        structure = parser.get_structure('structure', pdb_path)
    except Exception as e:
        print(f"无法解析 PDB 文件 {pdb_path}: {e}")
        return None

    ppb = PPBuilder()
    seq = ""
    for pp in ppb.build_peptides(structure):
        seq += str(pp.get_sequence())
        # 只取第一个多肽链
        break

    return seq if seq else None


def get_sequences(directory):
    """

    获取指定目录下所有 PDB 文件的序列。



    参数:

        directory (str): PDB 文件夹路径。



    返回:

        dict: 键为 PDB 文件名,值为其氨基酸序列。

    """
    seq_dict = {}
    for filename in os.listdir(directory):
        if filename.endswith('.pdb'):
            filepath = os.path.join(directory, filename)
            seq = extract_sequence(filepath)
            if seq:
                seq_dict[filename] = seq
            else:
                print(f"警告: 无法提取序列 {filename}")
    return seq_dict


def match_pdbs(wetlab_seqs, pdb_files_seqs):
    """

    根据序列内容匹配 PDB 文件。



    参数:

        wetlab_seqs (dict): wetlab_pdb 文件夹中的 PDB 文件及其序列。

        pdb_files_seqs (dict): pdb_files 文件夹中的 PDB 文件及其序列。



    返回:

        list of tuples: 每个元组包含 (wetlab_pdb, pdb_files_pdb)。

    """
    matches = []
    pdb_files_seqs_reverse = {}
    for pdb, seq in pdb_files_seqs.items():
        pdb_files_seqs_reverse.setdefault(seq, []).append(pdb)

    for wetlab_pdb, wetlab_seq in wetlab_seqs.items():
        matched_pdbs = pdb_files_seqs_reverse.get(wetlab_seq, [])
        if matched_pdbs:
            for matched_pdb in matched_pdbs:
                matches.append((wetlab_pdb, matched_pdb))
        else:
            print(f"警告: 在 pdb_files 中未找到匹配的序列 for {wetlab_pdb} {wetlab_seq}")
    return matches


def get_ca_atoms(pdb_path):
    """

    提取 PDB 文件中第一个链的所有 C-alpha 原子。



    参数:

        pdb_path (str): PDB 文件的路径。



    返回:

        list of Bio.PDB.Atom.Atom: C-alpha 原子列表,如果失败则返回 None。

    """
    parser = PDBParser(QUIET=True)
    try:
        structure = parser.get_structure('structure', pdb_path)
    except Exception as e:
        print(f"无法解析 PDB 文件 {pdb_path}: {e}")
        return None

    ca_atoms = []
    models = list(structure.get_models())
    if not models:
        print(f"警告: PDB 文件 {pdb_path} 没有模型。")
        return None
    first_model = models[0]

    chains = list(first_model.get_chains())
    if not chains:
        print(f"警告: PDB 文件 {pdb_path} 没有链。")
        return None
    first_chain = chains[0]

    for residue in first_chain:
        if is_aa(residue, standard=True):
            if 'CA' in residue:
                ca = residue['CA']
                ca_atoms.append(ca)

    return ca_atoms if ca_atoms else None


def calculate_rmsd(pdb_path1, pdb_path2):
    """

    计算两个 PDB 文件之间的 RMSD。



    参数:

        pdb_path1 (str): 第一个 PDB 文件路径。

        pdb_path2 (str): 第二个 PDB 文件路径。



    返回:

        float: RMSD 值,如果计算失败则返回 None。

    """
    atoms1 = get_ca_atoms(pdb_path1)
    atoms2 = get_ca_atoms(pdb_path2)

    if atoms1 is None or atoms2 is None:
        print(f"警告: 无法获取 C-alpha 原子 for {pdb_path1} and/or {pdb_path2}")
        return None

    if len(atoms1) != len(atoms2):
        print(f"警告: {os.path.basename(pdb_path1)}{os.path.basename(pdb_path2)} 的 C-alpha 原子数量不同 ({len(atoms1)} vs {len(atoms2)}).")
        return None

    # 创建 Superimposer 对象
    sup = Superimposer()
    sup.set_atoms(atoms1, atoms2)
    sup.apply(atoms2)  # 应用旋转和平移到 atoms2,如果需要
    rmsd = sup.rms

    return rmsd


def calculate_tm_score_tmtools(coords1, coords2, seq1, seq2):
    """

    使用 tmtools 计算两个坐标阵列之间的 TM-score。



    参数:

        coords1 (np.ndarray): 第一个坐标数组,形状为 N1 x 3。

        coords2 (np.ndarray): 第二个坐标数组,形状为 N2 x 3。

        seq1 (str): 第一个序列。

        seq2 (str): 第二个序列。



    返回:

        float: TM-score 值,如果计算失败则返回 None。

    """
    try:
        res = tm_align(coords1, coords2, seq1, seq2)
        tm_score = res.tm_norm_chain2  # 使用 tm_norm_chain2 作为 TM-score
        return tm_score
    except Exception as e:
        print(f"Error calculating TM-score with tmtools: {e}")
        return None


def process_pair_rmsd_tm(pdb1_path, pdb2_path, seq1, seq2):
    """

    处理一对 PDB 文件,计算 TM-score 和 RMSD。



    参数:

        pdb1_path (str): 第一个 PDB 文件路径。

        pdb2_path (str): 第二个 PDB 文件路径。

        seq1 (str): 第一个序列。

        seq2 (str): 第二个序列。



    返回:

        tuple: (PDB_ID, TM_score, RMSD)

    """
    pdb1 = os.path.basename(pdb1_path)
    pdb2 = os.path.basename(pdb2_path)

    # 获取 C-alpha 坐标
    atoms1 = get_ca_atoms(pdb1_path)
    atoms2 = get_ca_atoms(pdb2_path)

    if atoms1 is None or atoms2 is None:
        print(f"警告: 无法获取 C-alpha 原子 for {pdb1} and/or {pdb2}")
        return (f"{pdb1} vs {pdb2}", None, None)

    if len(atoms1) != len(atoms2):
        print(f"警告: {pdb1}{pdb2} 的 C-alpha 原子数量不同 ({len(atoms1)} vs {len(atoms2)}).")
        return (f"{pdb1} vs {pdb2}", None, None)

    # 提取坐标为 NumPy 数组
    coords1 = np.array([atom.get_coord() for atom in atoms1])
    coords2 = np.array([atom.get_coord() for atom in atoms2])

    # 计算 RMSD
    rmsd = calculate_rmsd(pdb1_path, pdb2_path)

    # 计算 TM-score
    tm_score = calculate_tm_score_tmtools(coords1, coords2, seq1, seq2)

    return (f"{pdb1} vs {pdb2}", tm_score, rmsd)


def main():
    # 配置文件夹路径
    wetlab_dir = "./wetlab_pdb"  # 替换为您的 wetlab_pdb 文件夹路径
    pdb_files_dir = "/home/ubuntu/alphafold3/output_pdb/"  # 替换为您的 pdb_files 文件夹路径
    output_csv = "alphafold3_tm_rmsd_results.csv"  # 输出的 CSV 文件名

    # 检查文件夹是否存在
    if not os.path.isdir(wetlab_dir):
        print(f"错误: 文件夹 '{wetlab_dir}' 不存在。")
        sys.exit(1)
    if not os.path.isdir(pdb_files_dir):
        print(f"错误: 文件夹 '{pdb_files_dir}' 不存在。")
        sys.exit(1)

    print("提取 wetlab_pdb 文件夹中的序列...")
    wetlab_seqs = get_sequences(wetlab_dir)
    print(f"提取到 {len(wetlab_seqs)} 个序列。\n")

    print("提取 pdb_files 文件夹中的序列...")
    pdb_files_seqs = get_sequences(pdb_files_dir)
    print(f"提取到 {len(pdb_files_seqs)} 个序列。\n")

    print("匹配 PDB 文件 based on sequences...")
    matches = match_pdbs(wetlab_seqs, pdb_files_seqs)
    print(f"找到 {len(matches)} 对匹配的 PDB 文件。\n")

    if not matches:
        print("没有找到任何匹配的 PDB 文件。")
        sys.exit(0)

    results = []

    print("开始计算 TM-score 和 RMSD...\n")
    for wetlab_pdb, ref_pdb in matches:
        print(f"处理: {wetlab_pdb} vs {ref_pdb}")
        wetlab_path = os.path.join(wetlab_dir, wetlab_pdb)
        ref_path = os.path.join(pdb_files_dir, ref_pdb)
        try:
            # 获取序列
            seq1 = wetlab_seqs[wetlab_pdb]
            seq2 = pdb_files_seqs[ref_pdb]

            pdb_id, tm_score, rmsd = process_pair_rmsd_tm(wetlab_path, ref_path, seq1, seq2)
            if tm_score is not None and rmsd is not None:
                print(f"TM-score: {tm_score:.4f}, RMSD: {rmsd:.4f} Å\n")
            else:
                print("计算失败。\n")
        except Exception as e:
            print(f"错误: 处理 {wetlab_pdb} vs {ref_pdb} 时出错: {e}\n")
            pdb_id, tm_score, rmsd = (f"{wetlab_pdb} vs {ref_pdb}", None, None)
        results.append((pdb_id, tm_score, rmsd))

    # 创建 DataFrame
    df = pd.DataFrame(results, columns=['PDB_ID', 'TM_score', 'RMSD'])

    # 过滤掉无法计算的结果
    valid_df = df.dropna(subset=['TM_score', 'RMSD'])

    if not valid_df.empty:
        # 计算平均值和标准差
        avg_tm = valid_df['TM_score'].mean()
        std_tm = valid_df['TM_score'].std()
        avg_rmsd = valid_df['RMSD'].mean()
        std_rmsd = valid_df['RMSD'].std()

        # 创建摘要 DataFrame
        summary = pd.DataFrame({
            'PDB_ID': ['Average', 'Std Dev'],
            'TM_score': [avg_tm, std_tm],
            'RMSD': [avg_rmsd, std_rmsd]
        })

        # 合并摘要和详细结果
        final_df = pd.concat([summary, df], ignore_index=True)
    else:
        final_df = df.copy()

    # 保存到 CSV
    final_df.to_csv(output_csv, index=False)
    print(f"结果已保存到 '{output_csv}'。")

    # 打印摘要
    if not valid_df.empty:
        print("\n### 计算摘要 ###")
        print(f"平均 TM-score: {avg_tm:.4f}")
        print(f"TM-score 标准差: {std_tm:.4f}")
        print(f"平均 RMSD: {avg_rmsd:.4f} Å")
        print(f"RMSD 标准差: {std_rmsd:.4f} Å")
    else:
        print("没有成功计算任何 PDB 对的 TM-score 和 RMSD。")


if __name__ == "__main__":
    main()