Spaces:
Running
Running
File size: 4,538 Bytes
9591ffa 9c0338a 9591ffa 9c0338a 9591ffa 9c0338a 9591ffa 9c0338a 9591ffa 9c0338a 9591ffa 9c0338a 9591ffa | 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 | """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)
|