# -*- coding: utf-8 -*- """ AfDesign Peptide Binder Design — Streamlit App Converted from ColabDesign notebook (binder_design.py) """ import os import re import warnings import tempfile import subprocess import tarfile import streamlit as st import numpy as np import requests import plotly.express as px warnings.simplefilter(action='ignore', category=FutureWarning) # ─────────────────────────────────────────────────────────────────────── # Page config & custom CSS # ─────────────────────────────────────────────────────────────────────── st.set_page_config( page_title="AfDesign · Peptide Binder Designer", page_icon="🧬", layout="wide", initial_sidebar_state="expanded", ) CUSTOM_CSS = """ """ st.markdown(CUSTOM_CSS, unsafe_allow_html=True) # ─────────────────────────────────────────────────────────────────────── # Helper: fetch PDB file # ─────────────────────────────────────────────────────────────────────── PARAMS_DIR = "params" @st.cache_data(show_spinner=False) def fetch_pdb(pdb_code: str) -> str: """Download a PDB from RCSB or AlphaFoldDB and return the local path.""" if os.path.isfile(pdb_code): return pdb_code if len(pdb_code) == 4: url = f"https://files.rcsb.org/view/{pdb_code}.pdb" out_path = f"{pdb_code}.pdb" else: url = f"https://alphafold.ebi.ac.uk/files/AF-{pdb_code}-F1-model_v3.pdb" out_path = f"AF-{pdb_code}-F1-model_v3.pdb" if not os.path.isfile(out_path): resp = requests.get(url, timeout=60) resp.raise_for_status() with open(out_path, "w") as f: f.write(resp.text) return out_path def download_params(status_container): """Download and extract AlphaFold parameters if not present.""" if os.path.isdir(PARAMS_DIR) and len(os.listdir(PARAMS_DIR)) > 0: return True PARAMS_URL = "https://storage.googleapis.com/alphafold/alphafold_params_2022-12-06.tar" TAR_FILE = "alphafold_params_2022-12-06.tar" try: os.makedirs(PARAMS_DIR, exist_ok=True) # Download with progress status_container.write("📦 Downloading AlphaFold parameters (~3.5 GB)... This only happens once.") progress_bar = status_container.progress(0, text="Downloading...") resp = requests.get(PARAMS_URL, stream=True, timeout=600) resp.raise_for_status() total = int(resp.headers.get('content-length', 0)) downloaded = 0 with open(TAR_FILE, 'wb') as f: for chunk in resp.iter_content(chunk_size=8 * 1024 * 1024): # 8MB chunks f.write(chunk) downloaded += len(chunk) if total > 0: pct = min(downloaded / total, 1.0) progress_bar.progress(pct, text=f"Downloaded {downloaded / 1e9:.1f} / {total / 1e9:.1f} GB") progress_bar.progress(1.0, text="Download complete!") # Extract status_container.write("📂 Extracting parameters...") with tarfile.open(TAR_FILE, 'r') as tar: tar.extractall(path=PARAMS_DIR) # Cleanup tar if os.path.isfile(TAR_FILE): os.remove(TAR_FILE) status_container.write("✅ AlphaFold parameters ready!") return True except Exception as e: status_container.error(f"Failed to download AlphaFold parameters: {e}") return False # ─────────────────────────────────────────────────────────────────────── # Hero header # ─────────────────────────────────────────────────────────────────────── st.markdown('
🧬 AfDesign · Peptide Binder Designer
', unsafe_allow_html=True) st.markdown( '' 'Generate / hallucinate a protein binder sequence that AlphaFold predicts will bind your target structure. ' 'Maximises interface contacts and binder pLDDT.' '
', unsafe_allow_html=True, ) # ─────────────────────────────────────────────────────────────────────── # Sidebar — Inputs # ─────────────────────────────────────────────────────────────────────── with st.sidebar: st.markdown("### 🎯 Target Info") pdb_code = st.text_input( "PDB / UniProt Code", value="5F9R", help="Enter a 4-letter PDB code (e.g. 5F9R), a UniProt code (to fetch from AlphaFoldDB), or leave blank and upload a file below.", ) uploaded_pdb = st.file_uploader("Or upload a PDB file", type=["pdb"]) target_chain = st.text_input("Target Chain", value="B", help="Chain identifier for the target protein.") target_hotspot = st.text_input( "Target Hotspot", value="", help='Restrict loss to specific positions on the target (e.g. "1-10,12,15"). Leave blank for no restriction.', ) target_flexible = st.toggle("Flexible Target Backbone", value=False, help="Allow backbone of target to be flexible during design.") st.markdown("---") st.markdown("### 🔗 Binder Info") binder_len = st.slider("Binder Length", min_value=10, max_value=200, value=25, step=1, help="Length of the binder peptide to hallucinate.") binder_seq_input = st.text_input( "Initial Binder Sequence", value="", help="Optional amino acid sequence to initialize the design. If provided, binder length is set to its length.", ) binder_chain_input = st.text_input( "Binder Chain (supervised)", value="", help="If defined, supervised loss is used and binder length is ignored.", ) st.markdown("---") st.markdown("### ⚙️ Model Configuration") use_multimer = st.toggle("Use AlphaFold-Multimer", value=False, help="Use the multimer model for design.") num_recycles = st.select_slider("Number of Recycles", options=[0, 1, 3, 6], value=1) num_models_sel = st.selectbox("Number of Models", options=["1", "2", "3", "4", "5", "all"], index=0, help="Number of trained models to use during optimization.") st.markdown("---") st.markdown("### 🚀 Optimization") optimizer = st.selectbox( "Optimizer", options=["pssm_semigreedy", "3stage", "semigreedy", "pssm", "logits", "soft", "hard"], index=0, help=( "• pssm_semigreedy — uses designed PSSM to bias semigreedy opt (recommended)\n" "• 3stage — gradient descent: logits → soft → hard\n" "• semigreedy — random mutations, accepts if loss decreases\n" "• pssm — GD logits→soft for a sequence profile\n" "• logits / soft / hard — raw GD optimization" ), ) with st.expander("Advanced GD Settings", expanded=False): gd_method = st.selectbox( "GD Method", options=[ "sgd", "adam", "adamw", "adabelief", "adafactor", "adagrad", "fromage", "lamb", "lars", "noisy_sgd", "dpsgd", "radam", "rmsprop", "sm3", "yogi", ], index=0, ) learning_rate = st.number_input("Learning Rate", min_value=0.0001, max_value=10.0, value=0.1, step=0.01, format="%.4f") norm_seq_grad = st.toggle("Normalize Sequence Gradient", value=True) dropout = st.toggle("Dropout", value=True) st.markdown("---") st.markdown("### 🎨 Visualization") color_mode = st.selectbox("Color Scheme", options=["pLDDT", "chain", "rainbow"], index=0) show_sidechains = st.toggle("Show Sidechains", value=False) show_mainchains = st.toggle("Show Mainchains", value=False) # ─────────────────────────────────────────────────────────────────────── # Process inputs # ─────────────────────────────────────────────────────────────────────── binder_seq = re.sub("[^A-Z]", "", binder_seq_input.upper()) if binder_seq_input else "" if len(binder_seq) > 0: binder_len_final = len(binder_seq) else: binder_seq = None binder_len_final = binder_len binder_chain = binder_chain_input if binder_chain_input.strip() else None hotspot = target_hotspot if target_hotspot.strip() else None num_models_int = 5 if num_models_sel == "all" else int(num_models_sel) # ─────────────────────────────────────────────────────────────────────── # Summary cards # ─────────────────────────────────────────────────────────────────────── col1, col2, col3 = st.columns(3) with col1: st.markdown( f"""📊 Results
', unsafe_allow_html=True) # Metrics log = model._tmp.get("best", {}).get("aux", {}).get("log", {}) metric_cols = st.columns(4) metrics_to_show = [ ("pLDDT (binder)", log.get("plddt", None)), ("pAE", log.get("pae", None)), ("i_pAE", log.get("i_pae", None)), ("i_con", log.get("i_con", None)), ] for col, (label, val) in zip(metric_cols, metrics_to_show): with col: display_val = f"{val:.3f}" if val is not None else "—" st.markdown( f"""