Spaces:
Sleeping
Sleeping
| """ | |
| HuggingFace model integration. | |
| Uses ESM-2 (facebook/esm2_t6_8M_UR50D) for protein sequence embeddings | |
| and structural property prediction. Lazy-loaded on first use. | |
| """ | |
| from __future__ import annotations | |
| import os, time | |
| import numpy as np | |
| _esm_model = None | |
| _esm_tokenizer = None | |
| MODEL_ID = os.getenv("ESM_MODEL", "facebook/esm2_t6_8M_UR50D") | |
| def _load_esm(): | |
| global _esm_model, _esm_tokenizer | |
| if _esm_model is not None: | |
| return | |
| try: | |
| from transformers import AutoTokenizer, AutoModel | |
| import torch | |
| _esm_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) | |
| _esm_model = AutoModel.from_pretrained(MODEL_ID) | |
| _esm_model.eval() | |
| except Exception as e: | |
| print(f"[models] ESM-2 load failed: {e}. Falling back to mock embeddings.") | |
| def embed_sequence(sequence: str) -> list[float]: | |
| """ | |
| Return a 320-dim embedding for the protein sequence. | |
| Uses ESM-2 if available, otherwise a deterministic hash-based mock. | |
| """ | |
| if not sequence: | |
| return [0.0] * 320 | |
| _load_esm() | |
| if _esm_model is not None: | |
| try: | |
| import torch | |
| seq = sequence[:1022] # ESM-2 max length | |
| inputs = _esm_tokenizer(seq, return_tensors="pt", truncation=True) | |
| with torch.no_grad(): | |
| out = _esm_model(**inputs) | |
| # Mean pool over residue dimension | |
| emb = out.last_hidden_state[0, 1:-1].mean(dim=0).numpy().tolist() | |
| return emb[:320] if len(emb) >= 320 else emb + [0.0] * (320 - len(emb)) | |
| except Exception as e: | |
| print(f"[models] ESM-2 inference error: {e}") | |
| # Deterministic fallback: hash-based mock embedding | |
| import hashlib | |
| h = hashlib.sha256(sequence.encode()).digest() | |
| rng = np.random.default_rng(int.from_bytes(h[:8], "big")) | |
| return rng.uniform(-1, 1, 320).tolist() | |
| def predict_disorder(sequence: str) -> list[float]: | |
| """ | |
| Predict per-residue disorder probability (0=ordered, 1=disordered). | |
| Simple heuristic using amino acid physicochemical properties. | |
| """ | |
| DISORDER_PRONE = set("GSEQKAPRT") | |
| ORDER_PRONE = set("CFYWILVMH") | |
| scores = [] | |
| for aa in sequence.upper(): | |
| if aa in DISORDER_PRONE: | |
| scores.append(0.7) | |
| elif aa in ORDER_PRONE: | |
| scores.append(0.2) | |
| else: | |
| scores.append(0.45) | |
| # Smooth with a sliding window | |
| w = 9 | |
| padded = [scores[0]] * (w // 2) + scores + [scores[-1]] * (w // 2) | |
| smoothed = [ | |
| sum(padded[i:i + w]) / w for i in range(len(scores)) | |
| ] | |
| return smoothed | |
| def classify_secondary_structure_heuristic(sequence: str) -> dict: | |
| """ | |
| Fast heuristic secondary structure estimate. | |
| Returns fraction of helix, sheet, coil residues. | |
| """ | |
| HELIX = set("AELMQKR") | |
| SHEET = set("VIFYW") | |
| total = len(sequence) or 1 | |
| h = sum(1 for aa in sequence.upper() if aa in HELIX) / total | |
| s = sum(1 for aa in sequence.upper() if aa in SHEET) / total | |
| return { | |
| "helix_fraction": round(h, 3), | |
| "sheet_fraction": round(s, 3), | |
| "coil_fraction": round(max(0, 1 - h - s), 3), | |
| } | |
| def cosine_similarity(a: list[float], b: list[float]) -> float: | |
| """Cosine similarity between two embedding vectors.""" | |
| a, b = np.array(a), np.array(b) | |
| norm = np.linalg.norm(a) * np.linalg.norm(b) | |
| return float(np.dot(a, b) / norm) if norm > 0 else 0.0 | |