File size: 5,295 Bytes
cd0c7a9 | 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 132 133 134 135 136 137 138 139 140 141 142 143 | """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
- EC number predictions with confidence scores
- Per-residue importance scores (saliency map)
"""
from __future__ import annotations
import json
import logging
import urllib.request
logger = logging.getLogger(__name__)
# GO term categories mapped from InterPro/UniProt keywords
_GO_MAPPINGS = {
"hydrolase": ("GO:0003824", "hydrolase activity", "MF"),
"transferase": ("GO:0016740", "transferase activity", "MF"),
"oxidoreductase": ("GO:0016491", "oxidoreductase activity", "MF"),
"lyase": ("GO:0016829", "lyase activity", "MF"),
"isomerase": ("GO:0016853", "isomerase activity", "MF"),
"ligase": ("GO:0016874", "ligase activity", "MF"),
"kinase": ("GO:0016301", "kinase activity", "MF"),
"protease": ("GO:0008233", "peptidase activity", "MF"),
"receptor": ("GO:0004872", "receptor activity", "MF"),
"binding": ("GO:0005488", "binding", "MF"),
"transporter": ("GO:0005215", "transporter activity", "MF"),
"signal": ("GO:0005515", "protein binding", "MF"),
"cytoplasm": ("GO:0005737", "cytoplasm", "CC"),
"nucleus": ("GO:0005634", "nucleus", "CC"),
"membrane": ("GO:0016020", "membrane", "CC"),
"mitochondrion": ("GO:0005739", "mitochondrion", "CC"),
"cell": ("GO:0005623", "cell", "CC"),
"response": ("GO:0050789", "regulation of biological process", "BP"),
"phosphorylation": ("GO:0016310", "phosphorylation", "BP"),
"transcription": ("GO:0006351", "transcription, DNA-templated", "BP"),
"translation": ("GO:0006412", "translation", "BP"),
"apoptosis": ("GO:0006915", "apoptotic process", "BP"),
"cell_cycle": ("GO:0007049", "cell cycle", "BP"),
"immune": ("GO:0006955", "immune response", "BP"),
}
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),
})
# 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))
return {
"pdb_id": pdb_id.upper(),
"sequence_length": seq_len,
"go_terms": predicted_go,
"ec_numbers": [],
"saliency": saliency,
"method": "heuristic_composition",
"note": "Predictions based on amino acid composition. 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)
|