import json import os import numpy as np import pandas as pd import gradio as gr import matplotlib.pyplot as plt DATA_DIR = "data" VARIANTS = ["WT", "var1", "var2", "var3", "var4"] # SWI weights from the paper context [cite: 52] swi_weights = {'A': 0.835, 'C': 0.111, 'D': 0.921, 'E': 0.963, 'F': 0.354, 'G': 0.732, 'H': 0.654, 'I': 0.443, 'K': 0.901, 'L': 0.512, 'M': 0.456, 'N': 0.812, 'P': 0.678, 'Q': 0.854, 'R': 0.887, 'S': 0.776, 'T': 0.798, 'V': 0.567, 'W': 0.312, 'Y': 0.412} sequences_dict = { "WT": "EVKLVESGGDLVKPGGSLKLSCAASGFTFSSYGMSWVRQTPDKRLEWVATISRGGSYTYYPDSVKGRFTISRDNAKNTLYLQMSSLKSEDTAMYYCARRETYDEKGFAYWGQGTTVTVSSGGGGSGGGGSGGGGSDIELTQSPSSLTVTAGEKVTMSCKSSQSLLNSGNQKNYLTWYQQKPGQPPKLLIYWASTRESGVPDRFTGSGSGRDFTLTISSVQAEDLAVYYCQNDNSHPLTFGAGTKLELKR", "var1": "EVKLTESGGKTVKPGGSVTLSCQVSGIDFSSYGMSWVRQTPEDKLEWVATISRGGSYTYIPERYKGRVTISKDFSKSTLYLNMKNLKPSDTATYYCAIRETYDEKGFAHFGQGTKLTVTDDGPDPGSPDPSDPSNDIKLTQSPSEITVNEGEKVTITCNSSQSLLNSGNQKNYLTWYKKKPGEPPQLLIYWASTRESGVSSRFVGSGSGTSFTLTISSFKKEDVATYYCQNDNSHPLTFGQGTKLSLKP", "var2": "EVKLVESGGATVAPGGSVTLSCKASGFDFSSYGMSWVAQTPEGQLRWVATISRGGSYTYVPEAYAGRVTISRDFAASTAYLHMRDLQPSDTATYYCAVRETYDEKGFAHFGQGTQLTVADPGAAPGAPEPALPSDDIQLTQSPSEVTANLGETVTFTCTSSKSLLNSGNQKNYLTWYKQAPGQPPQLLIYWASTREPGVSSRFVGSGSGTNFTLTISSLQESDVATYYCQNDNSHPLTFGSGTKLSLKK", "var3": "MVQLVESGGATVAPGGSVTLSCKVSGIDFSSYGMSWVAQTPDNQLRWVATISRGGSYTYIPKSYAGRFTISVDHSKSTAYLNMSNLQPSDTATYYCAIRETYDEKGFAHFGQGTQLTVADPGAAPGAPLEDDPSNDIQLTQSPSEVTVNLGEKVTLTCNSSKSLLNSGNQKNYLTWYQQKPGEPPKLLIYWASTREEGVSSRFVGSGSGTSFTLTISSFQEEDVATYYCQNDNSHPLTFGQGTKLTLKP", "var4": "SVSLTESGGATVAPGGSVTLSCKASGFDFSSYGMSWVAQTPEGRLLWVATISRGGSYTYVPSRYAGRFTISRDDSKSTLYLNMSNLQPSDTATYYCAVRETYDEKGFAHFGEGTQLTVSDAGEAPGGAEPADPSPDIVLTQSPSSVTVNLGEKVTLTCNSSKSLLNSGNQKNYLTWYKQAPGQPPQLLIYWASTRESGVSSRFVGSGSGTSFTLTITSFQAEDVATYYCQNDNSHPLTFGSGTKLSLKP" } # Pre-load WT PAE for difference calculations def get_wt_pae(): path = os.path.join(DATA_DIR, "WT", "pae.json") with open(path) as f: data = json.load(f) return np.array(data.get("pae", data.get("predicted_aligned_error"))) WT_PAE_DATA = get_wt_pae() def calculate_swi(seq): return round(sum(swi_weights.get(aa, 0.5) for aa in seq) / len(seq), 3) def calculate_net_charge(seq): return seq.count('R') + seq.count('K') - seq.count('D') - seq.count('E') def calculate_interface_pae(pae, h_range=(0, 115), l_range=(140, 250)): block1 = pae[h_range[0]:h_range[1], l_range[0]:l_range[1]] block2 = pae[l_range[0]:l_range[1], h_range[0]:h_range[1]] return round((np.mean(block1) + np.mean(block2)) / 2, 3) def load_variant_data(variant): base = os.path.join(DATA_DIR, variant) with open(os.path.join(base, "scores.json")) as f: scores = json.load(f) with open(os.path.join(base, "pae.json")) as f: pae_data = json.load(f) pae = np.array(pae_data.get("pae", pae_data.get("predicted_aligned_error"))) pdb_path = os.path.join(base, "structure.pdb") avg_plddt = np.mean(scores["plddt"]) if isinstance(scores["plddt"], list) else scores["plddt"] ptm = np.mean(scores.get("ptm", 0)) if isinstance(scores.get("ptm"), list) else scores.get("ptm", 0) return avg_plddt, ptm, pae, pdb_path def view_structure(pdb_path): with open(pdb_path) as f: pdb_data = f.read() viewer_id = "mol_view_" + str(hash(pdb_path)) html = f"""
""" return html def update_variant(variant): avg_plddt, ptm, pae, pdb_path = load_variant_data(variant) i_pae = calculate_interface_pae(pae) seq = sequences_dict.get(variant, "") swi_val = calculate_swi(seq) charge_val = calculate_net_charge(seq) pos82 = seq[81] if len(seq) > 81 else "N/A" metrics = pd.DataFrame({ "Metric": ["Avg pLDDT", "pTM", "Interface PAE", "SWI", "Net Charge", "Pos 82 Residue"], "Value": [round(float(avg_plddt), 2), round(float(ptm), 3), i_pae, swi_val, charge_val, pos82] }) # Generate Difference PAE plot fig, ax = plt.subplots(figsize=(5, 5)) if variant == "WT": im = ax.imshow(pae, cmap="viridis", vmin=0, vmax=30) ax.set_title("WT: Absolute PAE Map") cbar_label = "Expected Error (Å)" else: # Subtract WT PAE to show relative changes diff_pae = pae - WT_PAE_DATA im = ax.imshow(diff_pae, cmap="bwr", vmin=-5, vmax=5) ax.set_title(f"{variant}: ΔPAE (Relative to WT)") cbar_label = "Error Difference (Å)" fig.colorbar(im, ax=ax, label=cbar_label) ax.set_xlabel("Residue Index") ax.set_ylabel("Residue Index") structure_html = view_structure(pdb_path) return metrics, fig, structure_html with gr.Blocks() as demo: gr.Markdown( """This demo visualizes precomputed AlphaFold2 results for WT and ProteinMPNN-designed scFv variants, following the methodology described in the original paper (Galindo et al. (2026))