bio-nexus-api / app /tools /function_predict.py
Samad14's picture
fix(blast): poll cap 65min, DNA validation, program/db/max_hits params
cd0c7a9 verified
Raw
History Blame Contribute Delete
5.3 kB
"""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)