Spaces:
Running
Running
| """Protein function prediction using a simplified GCN-like approach. | |
| This is a lightweight approximation inspired by DeepFRI. For production use, | |
| bake the full DeepFRI weights into the Docker image (see Dockerfile additions). | |
| Outputs: | |
| - GO term predictions with confidence scores (MF / BP / CC) | |
| - Per-residue importance scores (saliency map) | |
| - Per-residue amino acid composition | |
| Note: EC number prediction is not implemented in the heuristic model. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import urllib.request | |
| logger = logging.getLogger(__name__) | |
| def _fetch_pdb_sequence(pdb_id: str) -> str: | |
| """Fetch the amino acid sequence for a PDB entry from RCSB.""" | |
| url = f"https://data.rcsb.org/rest/v1/core/polymer_entity/{pdb_id}/1" | |
| try: | |
| data = json.loads(urllib.request.urlopen(url, timeout=15).read()) | |
| return data.get("entity_poly", {}).get("pdbx_seq_one_letter_code_can", "") | |
| except Exception: | |
| pass | |
| # Fallback: fetch FASTA | |
| try: | |
| url = f"https://www.rcsb.org/fasta/entry/{pdb_id}" | |
| text = urllib.request.urlopen(url, timeout=15).read().decode() | |
| lines = [l for l in text.splitlines() if not l.startswith(">")] | |
| return "".join(lines).replace("\n", "") | |
| except Exception as e: | |
| raise RuntimeError(f"Could not fetch sequence for {pdb_id}: {e}") | |
| def _predict_from_sequence(sequence: str, pdb_id: str) -> dict: | |
| """Lightweight function prediction based on sequence composition. | |
| This is a heuristic approximation. Replace with proper GCN inference | |
| when DeepFRI weights are baked into the Docker image. | |
| """ | |
| seq_upper = sequence.upper() | |
| seq_len = len(seq_upper) | |
| aa_comp = {} | |
| for aa in seq_upper: | |
| aa_comp[aa] = aa_comp.get(aa, 0) + 1 | |
| # Predict GO terms based on amino acid composition patterns | |
| predicted_go = [] | |
| confidence_base = 0.5 | |
| # Simple composition-based predictions | |
| hydrophobic_fraction = sum(aa_comp.get(a, 0) for a in "AILMFWV") / max(seq_len, 1) | |
| charged_fraction = sum(aa_comp.get(a, 0) for a in "DEKRH") / max(seq_len, 1) | |
| if hydrophobic_fraction > 0.4: | |
| predicted_go.append({ | |
| "go_id": "GO:0016020", | |
| "name": "membrane", | |
| "namespace": "CC", | |
| "confidence": round(min(0.6 + hydrophobic_fraction * 0.3, 0.95), 3), | |
| }) | |
| if charged_fraction > 0.25: | |
| predicted_go.append({ | |
| "go_id": "GO:0005515", | |
| "name": "protein binding", | |
| "namespace": "MF", | |
| "confidence": round(min(0.55 + charged_fraction * 0.2, 0.9), 3), | |
| }) | |
| if hydrophobic_fraction > 0.4 and charged_fraction > 0.15: | |
| predicted_go.append({ | |
| "go_id": "GO:0007165", | |
| "name": "signal transduction", | |
| "namespace": "BP", | |
| "confidence": round(min(0.5 + hydrophobic_fraction * 0.2, 0.85), 3), | |
| }) | |
| # Always include a general prediction | |
| predicted_go.append({ | |
| "go_id": "GO:0003674", | |
| "name": "molecular_function", | |
| "namespace": "MF", | |
| "confidence": 0.99, | |
| }) | |
| # Per-residue importance (saliency approximation) | |
| # Higher importance at charged/polar residues on the surface | |
| saliency = [] | |
| for i, aa in enumerate(seq_upper): | |
| score = 0.1 | |
| if aa in "DEKRH": | |
| score = 0.6 | |
| elif aa in "STNQ": | |
| score = 0.4 | |
| elif aa in "AGV": | |
| score = 0.2 | |
| else: | |
| score = 0.15 | |
| saliency.append(round(score, 3)) | |
| # Actual per-residue composition (fractions), sorted most abundant first | |
| composition = { | |
| "aa": "ACDEFGHIKLMNPQRSTVWY", | |
| "fractions": {aa: round(aa_comp.get(aa, 0) / max(seq_len, 1), 4) for aa in "ACDEFGHIKLMNPQRSTVWY"}, | |
| } | |
| return { | |
| "pdb_id": pdb_id.upper(), | |
| "sequence_length": seq_len, | |
| "go_terms": predicted_go, | |
| "ec_numbers": [], | |
| "saliency": saliency, | |
| "composition": composition, | |
| "method": "heuristic_composition", | |
| "note": "Predictions based on amino acid composition. EC number prediction is not implemented in this heuristic model; for research-grade predictions use the full DeepFRI model.", | |
| } | |
| def predict_function(pdb_id: str) -> dict: | |
| """Main entry point: predict protein function from structure.""" | |
| sequence = _fetch_pdb_sequence(pdb_id) | |
| if not sequence: | |
| raise RuntimeError(f"No sequence available for PDB {pdb_id}") | |
| return _predict_from_sequence(sequence, pdb_id) | |