PeptideFoldEvaluation / evaluation.py
auralray's picture
Upload 81 files
0da47ab verified
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()